arkgate 2.12.0 → 2.13.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 (70) hide show
  1. package/CHANGELOG.md +83 -0
  2. package/README.md +55 -31
  3. package/bin/ark-check.mjs +95 -36
  4. package/bin/ark-mcp.mjs +11 -5
  5. package/bin/ark-shared.mjs +88 -56
  6. package/bin/ark.mjs +45 -10
  7. package/bin/lib/agent-gates.mjs +12 -0
  8. package/bin/lib/architecture-scan.mjs +8 -0
  9. package/bin/lib/ci-and-commands.mjs +9 -3
  10. package/bin/lib/codex-home.mjs +7 -0
  11. package/bin/lib/config-contract.mjs +331 -0
  12. package/bin/lib/doctor-plan.mjs +43 -16
  13. package/bin/lib/enforcement-profiles.mjs +97 -0
  14. package/bin/lib/host-support-matrix.mjs +77 -0
  15. package/bin/lib/install-migrate.mjs +45 -14
  16. package/bin/lib/mcp-adoption.mjs +35 -3
  17. package/bin/lib/open-html.mjs +75 -0
  18. package/bin/lib/presets.mjs +3 -2
  19. package/bin/lib/safety-diagnostics.mjs +31 -11
  20. package/bin/lib/skill-install.mjs +64 -0
  21. package/bin/lib/ts-resolve.mjs +2 -1
  22. package/bin/lib/weakest-link.mjs +417 -0
  23. package/bin/lib/write-path-capabilities.mjs +182 -0
  24. package/bin/lib/write-path-detect.mjs +62 -99
  25. package/dist/configContract-iBLxx5Tz.d.cts +53 -0
  26. package/dist/configContract-iBLxx5Tz.d.ts +53 -0
  27. package/dist/eslint/index.cjs +375 -13
  28. package/dist/eslint/index.cjs.map +1 -1
  29. package/dist/eslint/index.d.cts +30 -20
  30. package/dist/eslint/index.d.ts +30 -20
  31. package/dist/eslint/index.js +375 -13
  32. package/dist/eslint/index.js.map +1 -1
  33. package/dist/index.cjs +723 -61
  34. package/dist/index.cjs.map +1 -1
  35. package/dist/index.d.cts +95 -5
  36. package/dist/index.d.ts +95 -5
  37. package/dist/index.js +716 -61
  38. package/dist/index.js.map +1 -1
  39. package/dist/nestjs/index.cjs +150 -42
  40. package/dist/nestjs/index.cjs.map +1 -1
  41. package/dist/nestjs/index.d.cts +2 -1
  42. package/dist/nestjs/index.d.ts +2 -1
  43. package/dist/nestjs/index.js +150 -42
  44. package/dist/nestjs/index.js.map +1 -1
  45. package/dist/runtime/index.cjs +723 -61
  46. package/dist/runtime/index.cjs.map +1 -1
  47. package/dist/runtime/index.d.cts +3 -2
  48. package/dist/runtime/index.d.ts +3 -2
  49. package/dist/runtime/index.js +716 -61
  50. package/dist/runtime/index.js.map +1 -1
  51. package/dist/{types-BZ17b9i5.d.cts → types-BxBwnBpC.d.cts} +9 -36
  52. package/dist/{types-BZ17b9i5.d.ts → types-Wcs_l1_J.d.ts} +9 -36
  53. package/docs/agent-guide.md +32 -20
  54. package/docs/ai-gates.md +53 -18
  55. package/docs/configuration.md +97 -0
  56. package/docs/enthusiast/README.md +3 -3
  57. package/docs/enthusiast/how-to-agent-gates.md +7 -3
  58. package/docs/migrate-from-ark-runtime-kernel.md +3 -0
  59. package/docs/package-surface.md +14 -9
  60. package/docs/production-hardening.md +15 -2
  61. package/docs/threat-model.md +65 -0
  62. package/docs/typescript-support.md +3 -3
  63. package/package.json +15 -2
  64. package/schemas/ark.config.schema.json +750 -0
  65. package/server.json +2 -2
  66. package/templates/hooks/pre-commit-ark +37 -0
  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
@@ -216,6 +216,311 @@ 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
+
219
524
  // src/eslint/index.ts
220
525
  function lintedFilename(context) {
221
526
  if (typeof context.physicalFilename === "string" && context.physicalFilename.length > 0) {
@@ -247,14 +552,10 @@ function findConfigPath(startFile) {
247
552
  var _configCache = /* @__PURE__ */ new Map();
248
553
  function loadArkConfig(configPath) {
249
554
  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
- }
555
+ if (!import_node_fs.default.existsSync(configPath)) return null;
556
+ const config = parseArkConfigJson(import_node_fs.default.readFileSync(configPath, "utf8"), configPath).config;
557
+ _configCache.set(configPath, config);
558
+ return config;
258
559
  }
259
560
  function resolveRelativeImport(fromFile, specifier) {
260
561
  if (!specifier.startsWith(".")) return null;
@@ -285,6 +586,46 @@ function stringValue(node) {
285
586
  function propertyName(node) {
286
587
  return node?.name ?? stringValue(node);
287
588
  }
589
+ function sourceCodeFor(context) {
590
+ return context.sourceCode ?? context.getSourceCode?.();
591
+ }
592
+ function referenceFor(context, node) {
593
+ let scope = sourceCodeFor(context)?.getScope?.(node);
594
+ while (scope) {
595
+ const reference = scope.references?.find((candidate) => candidate.identifier === node);
596
+ if (reference) return reference;
597
+ scope = scope.upper ?? void 0;
598
+ }
599
+ return void 0;
600
+ }
601
+ function isLocallyBound(context, node, name) {
602
+ const reference = referenceFor(context, node);
603
+ if (reference?.resolved) return (reference.resolved.defs?.length ?? 0) > 0;
604
+ let scope = sourceCodeFor(context)?.getScope?.(node);
605
+ while (scope) {
606
+ const variable = scope.set?.get(name);
607
+ if (variable) return (variable.defs?.length ?? 0) > 0;
608
+ scope = scope.upper ?? void 0;
609
+ }
610
+ return false;
611
+ }
612
+ function isValueIdentifierReference(context, node) {
613
+ const reference = referenceFor(context, node);
614
+ if (reference) return reference.isValueReference !== false;
615
+ return node.parent?.type === "VariableDeclarator" && node.parent.init === node;
616
+ }
617
+ function memberExpressionPath(node) {
618
+ if (node?.type === "Identifier" && node.name) {
619
+ return { root: node, segments: [node.name] };
620
+ }
621
+ if (!node) return void 0;
622
+ const memberLike = node.type === "MemberExpression" || Boolean(node.object && node.property);
623
+ if (!memberLike || node.computed === true) return void 0;
624
+ const base = memberExpressionPath(node.object);
625
+ const property = propertyName(node.property);
626
+ if (!base || !property) return void 0;
627
+ return { root: base.root, segments: [...base.segments, property] };
628
+ }
288
629
  function calleePropertyName(node) {
289
630
  return propertyName(node.callee?.property);
290
631
  }
@@ -477,6 +818,7 @@ var noForbiddenGlobals = {
477
818
  if (!globals) {
478
819
  return {};
479
820
  }
821
+ const scopeAware = typeof sourceCodeFor(context)?.getScope === "function";
480
822
  const report = (node, name) => context.report({
481
823
  node,
482
824
  messageId: config ? "forbiddenGlobal" : "forbiddenGlobalDefault",
@@ -484,19 +826,39 @@ var noForbiddenGlobals = {
484
826
  });
485
827
  return {
486
828
  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);
829
+ if (node.parent?.type === "MemberExpression" && node.parent.object === node) return;
830
+ const path2 = memberExpressionPath(node);
831
+ if (!path2 || isLocallyBound(context, path2.root, path2.segments[0])) return;
832
+ const explicitGlobalThis = path2.segments[0] === "globalThis";
833
+ const normalized = explicitGlobalThis ? path2.segments.slice(1) : path2.segments;
834
+ let match;
835
+ for (let length = normalized.length; length >= (explicitGlobalThis ? 1 : 2); length -= 1) {
836
+ const candidate = normalized.slice(0, length).join(".");
837
+ if (globals.has(candidate)) {
838
+ match = candidate;
839
+ break;
840
+ }
841
+ }
842
+ if (match) report(node, match);
843
+ else if (!scopeAware && globals.has(path2.segments[0])) {
844
+ report(node, path2.segments[0]);
845
+ }
492
846
  },
493
847
  CallExpression(node) {
848
+ if (scopeAware) return;
494
849
  const callee = node.callee?.type === "Identifier" ? node.callee.name : void 0;
495
850
  if (callee && globals.has(callee)) report(node, callee);
496
851
  },
497
852
  NewExpression(node) {
853
+ if (scopeAware) return;
498
854
  const callee = node.callee?.type === "Identifier" ? node.callee.name : void 0;
499
855
  if (callee && globals.has(callee)) report(node, callee);
856
+ },
857
+ Identifier(node) {
858
+ if (!scopeAware || !node.name || !globals.has(node.name) || !isValueIdentifierReference(context, node) || isLocallyBound(context, node, node.name)) {
859
+ return;
860
+ }
861
+ report(node, node.name);
500
862
  }
501
863
  };
502
864
  }