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.
- package/CHANGELOG.md +122 -0
- package/README.md +90 -51
- package/bin/ark-check.mjs +156 -39
- package/bin/ark-mcp.mjs +119 -6
- package/bin/ark-shared.mjs +216 -129
- package/bin/ark.mjs +134 -34
- package/bin/lib/adapter-contract.mjs +93 -0
- package/bin/lib/agent-gates.mjs +13 -0
- package/bin/lib/analysis-engine.mjs +1171 -0
- package/bin/lib/architecture-scan.mjs +84 -127
- package/bin/lib/ci-and-commands.mjs +40 -3
- package/bin/lib/codex-home.mjs +7 -0
- package/bin/lib/config-contract.mjs +331 -0
- package/bin/lib/config-warnings.mjs +7 -205
- package/bin/lib/doctor-plan.mjs +43 -16
- package/bin/lib/enforcement-profiles.mjs +97 -0
- package/bin/lib/field-install.mjs +67 -10
- package/bin/lib/gate-files.mjs +42 -3
- package/bin/lib/graph-cycles.mjs +4 -54
- package/bin/lib/hook-templates.mjs +33 -1
- package/bin/lib/host-support-matrix.mjs +83 -0
- package/bin/lib/install-migrate.mjs +99 -30
- package/bin/lib/mcp-adoption.mjs +35 -3
- package/bin/lib/open-html.mjs +75 -0
- package/bin/lib/presets.mjs +45 -4
- package/bin/lib/safety-diagnostics.mjs +36 -15
- package/bin/lib/scan-files.mjs +12 -1
- package/bin/lib/skill-install.mjs +72 -1
- package/bin/lib/source-policy.mjs +36 -0
- package/bin/lib/start-preview.mjs +271 -0
- package/bin/lib/ts-resolve.mjs +13 -3
- package/bin/lib/weakest-link.mjs +417 -0
- package/bin/lib/write-path-capabilities.mjs +186 -0
- package/bin/lib/write-path-detect.mjs +62 -99
- package/compat/nestjs.cjs +2 -0
- package/compat/nestjs.d.ts +2 -0
- package/compat/nestjs.js +1 -0
- package/compat/runtime.cjs +2 -0
- package/compat/runtime.d.ts +2 -0
- package/compat/runtime.js +1 -0
- package/dist/configContract-BxSIwVRo.d.cts +259 -0
- package/dist/configContract-BxSIwVRo.d.ts +259 -0
- package/dist/eslint/index.cjs +500 -61
- package/dist/eslint/index.d.cts +36 -20
- package/dist/eslint/index.d.ts +36 -20
- package/dist/eslint/index.js +500 -61
- package/dist/index.cjs +1349 -2741
- package/dist/index.d.cts +449 -483
- package/dist/index.d.ts +449 -483
- package/dist/index.js +1325 -2687
- package/docs/agent-guide.md +58 -34
- package/docs/ai-gates.md +79 -21
- package/docs/configuration.md +97 -0
- package/docs/enthusiast/README.md +3 -3
- package/docs/enthusiast/how-to-agent-gates.md +7 -3
- package/docs/migrate-from-ark-runtime-kernel.md +5 -3
- package/docs/package-surface.md +19 -19
- package/docs/production-hardening.md +31 -5
- package/docs/threat-model.md +65 -0
- package/docs/typescript-support.md +30 -3
- package/package.json +46 -11
- package/schemas/ark.analysis-result.schema.json +91 -0
- package/schemas/ark.config.schema.json +750 -0
- package/server.json +2 -2
- package/templates/hooks/pre-commit-ark +37 -0
- package/templates/skills/ark-architect.md +3 -2
- package/templates/skills/ark-coverage.md +2 -2
- package/templates/skills/ark-runtime.md +8 -5
- package/templates/skills/ark-upgrade.md +36 -16
- package/tests/fixtures/ts-consumer/ark.config.json +2 -0
- package/dist/eslint/index.cjs.map +0 -1
- package/dist/eslint/index.js.map +0 -1
- package/dist/index.cjs.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/nestjs/index.cjs +0 -2498
- package/dist/nestjs/index.cjs.map +0 -1
- package/dist/nestjs/index.d.cts +0 -22
- package/dist/nestjs/index.d.ts +0 -22
- package/dist/nestjs/index.js +0 -2474
- package/dist/nestjs/index.js.map +0 -1
- package/dist/runtime/index.cjs +0 -3352
- package/dist/runtime/index.cjs.map +0 -1
- package/dist/runtime/index.d.cts +0 -2
- package/dist/runtime/index.d.ts +0 -2
- package/dist/runtime/index.js +0 -3270
- package/dist/runtime/index.js.map +0 -1
- package/dist/types-BZ17b9i5.d.cts +0 -1068
- package/dist/types-BZ17b9i5.d.ts +0 -1068
package/dist/eslint/index.cjs
CHANGED
|
@@ -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
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
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).
|
|
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
|
-
|
|
747
|
+
reportAdapterDiagnostic(
|
|
748
|
+
context,
|
|
365
749
|
node,
|
|
366
|
-
|
|
367
|
-
|
|
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
|
-
|
|
401
|
-
|
|
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
|
-
|
|
425
|
-
|
|
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
|
|
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
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
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
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
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
|