@comity-dev/eslint-plugin 0.1.0 → 0.1.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@comity-dev/eslint-plugin",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Reusable ESLint rules for Comity source-code policy. Development tooling.",
5
5
  "type": "module",
6
6
  "private": false,
@@ -8,6 +8,11 @@
8
8
  "comity": {
9
9
  "layer": "dev-tooling"
10
10
  },
11
+ "repository": {
12
+ "type": "git",
13
+ "url": "https://github.com/comityjs/development.git",
14
+ "directory": "packages/eslint-plugin"
15
+ },
11
16
  "engines": {
12
17
  "node": ">=24.0.0"
13
18
  },
package/dist/index.d.ts DELETED
@@ -1,20 +0,0 @@
1
- /**
2
- * @comity-dev/eslint-plugin — Reusable ESLint rules for Comity source-code policy.
3
- *
4
- * This package owns the canonical ESLint rules for Comity. Repositories
5
- * consume these rules via the shared orchestrator; they MUST NOT redefine
6
- * source-code policy.
7
- *
8
- * Standard references:
9
- * - architecture-validation.md §4
10
- * - public-api.md §3.2 (forbidden subpath imports)
11
- * - layering-policy.md §8 (rendering core MUST NOT depend on HTTP runtime)
12
- * - coding.md (general style policy)
13
- *
14
- * Public surface (intentional):
15
- * - default export: the ESLint plugin (loaded into flat-config)
16
- * - `recommended`: the canonical Comity rule set
17
- * - `RULE_IDS`: stable list of rule identifiers
18
- */
19
- export { plugin as default, RULE_IDS } from "./plugin.js";
20
- export { recommended } from "./recommended.js";
package/dist/index.js DELETED
@@ -1,20 +0,0 @@
1
- /**
2
- * @comity-dev/eslint-plugin — Reusable ESLint rules for Comity source-code policy.
3
- *
4
- * This package owns the canonical ESLint rules for Comity. Repositories
5
- * consume these rules via the shared orchestrator; they MUST NOT redefine
6
- * source-code policy.
7
- *
8
- * Standard references:
9
- * - architecture-validation.md §4
10
- * - public-api.md §3.2 (forbidden subpath imports)
11
- * - layering-policy.md §8 (rendering core MUST NOT depend on HTTP runtime)
12
- * - coding.md (general style policy)
13
- *
14
- * Public surface (intentional):
15
- * - default export: the ESLint plugin (loaded into flat-config)
16
- * - `recommended`: the canonical Comity rule set
17
- * - `RULE_IDS`: stable list of rule identifiers
18
- */
19
- export { plugin as default, RULE_IDS } from "./plugin.js";
20
- export { recommended } from "./recommended.js";
package/dist/plugin.d.ts DELETED
@@ -1,28 +0,0 @@
1
- /**
2
- * @comity-dev/eslint-plugin — Reusable ESLint rules for Comity source-code policy.
3
- *
4
- * This package owns the canonical ESLint rules for Comity. Repositories
5
- * consume these rules via the shared orchestrator; they MUST NOT redefine
6
- * source-code policy.
7
- *
8
- * Standard references:
9
- * - architecture-validation.md §4
10
- * - public-api.md §3.2 (forbidden subpath imports)
11
- * - layering-policy.md §8 (rendering core MUST NOT depend on HTTP runtime)
12
- * - coding.md (general style policy)
13
- *
14
- * Public surface (intentional):
15
- * - default export: the ESLint plugin (loaded into flat-config)
16
- * - `recommended`: the canonical Comity rule set
17
- * - `RULE_IDS`: stable list of rule identifiers
18
- */
19
- export interface BuiltinRule {
20
- /** The stable identifier of the rule */
21
- name: string;
22
- /** The rule implementation */
23
- rule: unknown;
24
- }
25
- export declare const BUILTIN_RULES: BuiltinRule[];
26
- export declare const plugin: import("eslint").ESLint.Plugin;
27
- /** Stable list of rule identifiers exported by this plugin. */
28
- export declare const RULE_IDS: string[];
package/dist/plugin.js DELETED
@@ -1,41 +0,0 @@
1
- /**
2
- * @comity-dev/eslint-plugin — Reusable ESLint rules for Comity source-code policy.
3
- *
4
- * This package owns the canonical ESLint rules for Comity. Repositories
5
- * consume these rules via the shared orchestrator; they MUST NOT redefine
6
- * source-code policy.
7
- *
8
- * Standard references:
9
- * - architecture-validation.md §4
10
- * - public-api.md §3.2 (forbidden subpath imports)
11
- * - layering-policy.md §8 (rendering core MUST NOT depend on HTTP runtime)
12
- * - coding.md (general style policy)
13
- *
14
- * Public surface (intentional):
15
- * - default export: the ESLint plugin (loaded into flat-config)
16
- * - `recommended`: the canonical Comity rule set
17
- * - `RULE_IDS`: stable list of rule identifiers
18
- */
19
- import noCryptoInCore from "./rules/no-crypto-in-core.js";
20
- import noDateInCore from "./rules/no-date-in-core.js";
21
- import noForbiddenDeepImport from "./rules/no-forbidden-deep-import.js";
22
- import noForbiddenSubpathImport from "./rules/no-forbidden-subpath-import.js";
23
- import noLayerViolation from "./rules/no-layer-violation.js";
24
- import noRenderCoreHttpRuntime from "./rules/no-render-core-http-runtime.js";
25
- export const BUILTIN_RULES = [
26
- { name: "no-forbidden-deep-import", rule: noForbiddenDeepImport },
27
- { name: "no-layer-violation", rule: noLayerViolation },
28
- { name: "no-date-in-core", rule: noDateInCore },
29
- { name: "no-crypto-in-core", rule: noCryptoInCore },
30
- { name: "no-forbidden-subpath-import", rule: noForbiddenSubpathImport },
31
- { name: "no-render-core-http-runtime", rule: noRenderCoreHttpRuntime },
32
- ];
33
- export const plugin = {
34
- meta: {
35
- name: "@comity-dev/eslint-plugin",
36
- version: "0.1.0",
37
- },
38
- rules: Object.fromEntries(BUILTIN_RULES.map((r) => [r.name, r.rule])),
39
- };
40
- /** Stable list of rule identifiers exported by this plugin. */
41
- export const RULE_IDS = BUILTIN_RULES.map((r) => r.name);
@@ -1,9 +0,0 @@
1
- /**
2
- * Recommended Comity ESLint rule set.
3
- *
4
- * The "recommended" preset bundles the canonical Comity rules with their
5
- * default severities. Repositories MAY override severities; they MUST NOT
6
- * disable rules silently.
7
- */
8
- import type { Linter } from "eslint";
9
- export declare const recommended: Linter.Config;
@@ -1,10 +0,0 @@
1
- export const recommended = {
2
- rules: {
3
- "@comity-dev/no-forbidden-deep-import": "error",
4
- "@comity-dev/no-layer-violation": "error",
5
- "@comity-dev/no-date-in-core": "error",
6
- "@comity-dev/no-crypto-in-core": "error",
7
- "@comity-dev/no-forbidden-subpath-import": "error",
8
- "@comity-dev/no-render-core-http-runtime": "error",
9
- },
10
- };
@@ -1,14 +0,0 @@
1
- /**
2
- * @comity-dev/no-crypto-in-core
3
- *
4
- * Forbid Node's `crypto` and WebCrypto (`globalThis.crypto`) modules in Core
5
- * packages. Core must be deterministic and side-effect free; cryptography
6
- * belongs in Adapters that own runtime concerns.
7
- *
8
- * Standard references:
9
- * - layering-policy.md §2.2 (Core boundaries)
10
- * - adapters.md
11
- */
12
- type Rule = unknown;
13
- declare const rule: Rule;
14
- export default rule;
@@ -1,123 +0,0 @@
1
- /**
2
- * @comity-dev/no-crypto-in-core
3
- *
4
- * Forbid Node's `crypto` and WebCrypto (`globalThis.crypto`) modules in Core
5
- * packages. Core must be deterministic and side-effect free; cryptography
6
- * belongs in Adapters that own runtime concerns.
7
- *
8
- * Standard references:
9
- * - layering-policy.md §2.2 (Core boundaries)
10
- * - adapters.md
11
- */
12
- const FORBIDDEN_SPECIFIERS = new Set(["crypto", "node:crypto", "webcrypto"]);
13
- const messages = {
14
- forbiddenImport: "Importing '{{specifier}}' is forbidden in Core (layering-policy.md §2.2). Use a Technology Adapter instead.",
15
- forbiddenGlobal: "Accessing the `crypto` global is forbidden in Core (layering-policy.md §2.2). Use a Technology Adapter instead.",
16
- };
17
- function isInCorePackage(filename) {
18
- return (filename.includes("/packages/") &&
19
- (filename.includes("/primitives/") ||
20
- filename.includes("/kernel/") ||
21
- filename.includes("/composition/") ||
22
- filename.includes("/http/") ||
23
- filename.includes("/router/") ||
24
- filename.includes("/html/") ||
25
- filename.includes("/hydration/") ||
26
- filename.includes("/seo/") ||
27
- filename.includes("/content/") ||
28
- filename.includes("/media/") ||
29
- filename.includes("/search/") ||
30
- filename.includes("/cache/") ||
31
- filename.includes("/auth/") ||
32
- filename.includes("/catalog/") ||
33
- filename.includes("/customer/") ||
34
- filename.includes("/inventory/") ||
35
- filename.includes("/order/") ||
36
- filename.includes("/payment/") ||
37
- filename.includes("/pricing/") ||
38
- filename.includes("/storefront/") ||
39
- filename.includes("/taxonomy/") ||
40
- filename.includes("/validation/") ||
41
- filename.includes("/address/") ||
42
- filename.includes("/geography/") ||
43
- filename.includes("/identity/") ||
44
- filename.includes("/organization/")));
45
- }
46
- const rule = {
47
- meta: {
48
- type: "problem",
49
- docs: {
50
- description: "Forbid `crypto` imports in Core packages — cryptography belongs in Adapters.",
51
- category: "Comity Architecture",
52
- recommended: true,
53
- },
54
- schema: [
55
- {
56
- type: "object",
57
- properties: {
58
- sourceName: {
59
- type: "string",
60
- description: "Name of the source package (e.g., '@comity/auth'). Used to scope the rule.",
61
- },
62
- },
63
- additionalProperties: false,
64
- },
65
- ],
66
- messages,
67
- },
68
- create(context) {
69
- const opts = (context.options[0] ?? {});
70
- const sourceName = opts.sourceName ?? null;
71
- // If sourceName provided, use it for classification; otherwise fall back to filename
72
- const inCore = sourceName
73
- ? sourceName.startsWith("@comity/") &&
74
- !["@comity/cli", "@comity/graphql-builder"].includes(sourceName)
75
- : isInCorePackage(context.filename ?? context.getFilename());
76
- // Disable this rule outside Core packages
77
- if (!inCore) {
78
- return {};
79
- }
80
- function checkSpecifier(value) {
81
- if (!value)
82
- return false;
83
- return FORBIDDEN_SPECIFIERS.has(value);
84
- }
85
- return {
86
- ImportDeclaration(node) {
87
- if (checkSpecifier(node.source.value)) {
88
- context.report({
89
- node,
90
- messageId: "forbiddenImport",
91
- data: { specifier: node.source.value },
92
- });
93
- }
94
- },
95
- ImportExpression(node) {
96
- // dynamic import()
97
- if (node.source.type === "Literal" &&
98
- typeof node.source.value === "string" &&
99
- checkSpecifier(node.source.value)) {
100
- context.report({
101
- node,
102
- messageId: "forbiddenImport",
103
- data: { specifier: node.source.value },
104
- });
105
- }
106
- },
107
- Identifier(node) {
108
- if (node.name !== "crypto")
109
- return;
110
- // Top-level access of the `crypto` global
111
- const parent = node.parent;
112
- if (!parent)
113
- return;
114
- if (parent.type === "ExpressionStatement" ||
115
- parent.type === "CallExpression" ||
116
- parent.type === "MemberExpression") {
117
- context.report({ node, messageId: "forbiddenGlobal" });
118
- }
119
- },
120
- };
121
- },
122
- };
123
- export default rule;
@@ -1,19 +0,0 @@
1
- /**
2
- * @comity-dev/no-date-in-core
3
- *
4
- * Forbid `Date`, `performance.now`, and `Math.random` access in Core and
5
- * Kernel packages — they introduce non-deterministic behavior that breaks
6
- * the Comity reproducibility contract.
7
- *
8
- * Legitimate exceptions:
9
- * - Telemetry/timing measurements using `performance.now()` (observational, not domain logic)
10
- * - Timestamp generation using `Date.now()` or `Date` constructor for logging/metadata
11
- * - Time primitives in `@comity/primitives/time` (explicitly allowed)
12
- *
13
- * Standard references:
14
- * - layering-policy.md §6 (determinism)
15
- * - domain-modeling.md
16
- */
17
- type Rule = unknown;
18
- declare const rule: Rule;
19
- export default rule;
@@ -1,163 +0,0 @@
1
- /**
2
- * @comity-dev/no-date-in-core
3
- *
4
- * Forbid `Date`, `performance.now`, and `Math.random` access in Core and
5
- * Kernel packages — they introduce non-deterministic behavior that breaks
6
- * the Comity reproducibility contract.
7
- *
8
- * Legitimate exceptions:
9
- * - Telemetry/timing measurements using `performance.now()` (observational, not domain logic)
10
- * - Timestamp generation using `Date.now()` or `Date` constructor for logging/metadata
11
- * - Time primitives in `@comity/primitives/time` (explicitly allowed)
12
- *
13
- * Standard references:
14
- * - layering-policy.md §6 (determinism)
15
- * - domain-modeling.md
16
- */
17
- const FORBIDDEN_GLOBALS = new Set(["Date", "performance"]);
18
- const FORBIDDEN_PROPERTIES = {
19
- Math: ["random"],
20
- };
21
- // Package names that are explicitly allowed to use Date/performance for telemetry
22
- const TELEMETRY_ALLOWLIST = new Set([
23
- "@comity/primitives", // time/instant primitives
24
- "@comity/http", // facade timing
25
- "@comity/html", // renderer pipeline timing
26
- "@comity/hydration", // browser capability implementations (createBrowserHydrationCapabilities, createNoopHydrationCapabilities)
27
- ]);
28
- // Allowlist for specific property access patterns that are legitimate telemetry
29
- const TELEMETRY_PROPERTY_ALLOWLIST = new Set([
30
- "performance.now",
31
- "Date.now",
32
- ]);
33
- const messages = {
34
- forbiddenGlobal: "Non-deterministic global '{{name}}' is forbidden in Core / Kernel (layering-policy.md §6). Use telemetry allowlist or primitives/time for timing.",
35
- forbiddenProperty: "Non-deterministic '{{object}}.{{property}}' is forbidden in Core / Kernel (layering-policy.md §6).",
36
- };
37
- function isInCoreOrKernelPackage(filename) {
38
- return (filename.includes("/packages/") &&
39
- (filename.includes("/primitives/") ||
40
- filename.includes("/kernel/") ||
41
- filename.includes("/composition/") ||
42
- filename.includes("/http/") ||
43
- filename.includes("/router/") ||
44
- filename.includes("/html/") ||
45
- filename.includes("/hydration/") ||
46
- filename.includes("/seo/") ||
47
- filename.includes("/content/") ||
48
- filename.includes("/media/") ||
49
- filename.includes("/search/")));
50
- }
51
- function isTelemetryAllowedPackage(filename) {
52
- return Array.from(TELEMETRY_ALLOWLIST).some((pkg) => filename.includes(`/packages/${pkg.replace("@comity/", "")}/`) || filename.includes(`/community/packages/${pkg.replace("@comity/", "")}/`));
53
- }
54
- function isTelemetryPropertyAccess(objectName, propertyName) {
55
- return TELEMETRY_PROPERTY_ALLOWLIST.has(`${objectName}.${propertyName}`);
56
- }
57
- const rule = {
58
- meta: {
59
- type: "problem",
60
- docs: {
61
- description: "Forbid Date / performance.now / Math.random in Core and Kernel packages.",
62
- category: "Comity Architecture",
63
- recommended: true,
64
- },
65
- schema: [
66
- {
67
- type: "object",
68
- properties: {
69
- sourceName: {
70
- type: "string",
71
- description: "Name of the source package (e.g., '@comity/http'). Used to scope the rule.",
72
- },
73
- },
74
- additionalProperties: false,
75
- },
76
- ],
77
- messages,
78
- },
79
- create(context) {
80
- const opts = (context.options[0] ?? {});
81
- const sourceName = opts.sourceName ?? null;
82
- // If sourceName provided, use it for classification; otherwise fall back to filename
83
- const inCoreOrKernel = sourceName
84
- ? sourceName.startsWith("@comity/") &&
85
- !["@comity/cli", "@comity/graphql-builder"].includes(sourceName) // these are not core/kernel
86
- : isInCoreOrKernelPackage(context.filename ?? context.getFilename());
87
- // Disable this rule outside Core/Kernel packages
88
- if (!inCoreOrKernel) {
89
- return {};
90
- }
91
- // Check if this package is allowed to use telemetry patterns
92
- const allowTelemetry = sourceName
93
- ? TELEMETRY_ALLOWLIST.has(sourceName)
94
- : isTelemetryAllowedPackage(context.filename ?? context.getFilename());
95
- return {
96
- Identifier(node) {
97
- // Detect globals like `Date` and `performance`
98
- if ((node.name === "Date" || node.name === "performance") &&
99
- // Only top-level usage (not as property of another object)
100
- node.parent &&
101
- (node.parent.type === "ExpressionStatement" ||
102
- node.parent.type === "VariableDeclarator" ||
103
- node.parent.type === "CallExpression" ||
104
- node.parent.type === "NewExpression" ||
105
- node.parent.type === "MemberExpression")) {
106
- // Allow telemetry patterns in allowlisted packages
107
- if (allowTelemetry && isTelemetryPropertyAccess(node.parent.object?.name ?? "", node.name)) {
108
- return;
109
- }
110
- // Allow Date.now() and performance.now() in telemetry allowlisted packages
111
- if (allowTelemetry &&
112
- ((node.name === "Date" && node.parent.type === "MemberExpression" && node.parent.property?.name === "now") ||
113
- (node.name === "performance" && node.parent.type === "MemberExpression" && node.parent.property?.name === "now"))) {
114
- return;
115
- }
116
- // Also handle optional chaining: window?.performance?.now()
117
- // Walk up the ancestor chain to find a MemberExpression with property 'now'
118
- if (allowTelemetry && (node.name === "Date" || node.name === "performance")) {
119
- let current = node.parent;
120
- while (current) {
121
- if (current.type === "MemberExpression" && current.property?.name === "now") {
122
- return;
123
- }
124
- if (current.type === "ChainExpression") {
125
- current = current.expression;
126
- continue;
127
- }
128
- current = current.parent;
129
- }
130
- }
131
- if (FORBIDDEN_GLOBALS.has(node.name)) {
132
- context.report({
133
- node,
134
- messageId: "forbiddenGlobal",
135
- data: { name: node.name },
136
- });
137
- }
138
- }
139
- // Detect property access like Math.random
140
- if (node.parent &&
141
- node.parent.type === "MemberExpression" &&
142
- node.parent.property === node) {
143
- const obj = node.parent.object;
144
- if (obj.type === "Identifier") {
145
- const allowed = FORBIDDEN_PROPERTIES[obj.name];
146
- if (allowed && allowed.includes(node.name)) {
147
- // Allow Math.random in primitives/time (explicit time primitive)
148
- if (allowTelemetry && sourceName === "@comity/primitives") {
149
- return;
150
- }
151
- context.report({
152
- node,
153
- messageId: "forbiddenProperty",
154
- data: { object: obj.name, property: node.name },
155
- });
156
- }
157
- }
158
- }
159
- },
160
- };
161
- },
162
- };
163
- export default rule;
@@ -1,20 +0,0 @@
1
- /**
2
- * @comity-dev/no-forbidden-deep-import
3
- *
4
- * Forbid deep imports into another `@comity/*` package's internals.
5
- *
6
- * Examples:
7
- * import { foo } from "@comity/pricing/src/foo" // ❌
8
- * import { foo } from "@comity/pricing/dist/foo.js" // ❌
9
- * import { foo } from "@comity/pricing/internal/x" // ❌
10
- * import { foo } from "@comity/pricing/lazy/x" // ❌
11
- * import { foo } from "@comity/pricing" // ✅
12
- * import { foo } from "@comity/pricing/exports/foo" // ✅
13
- *
14
- * Standard references:
15
- * - public-api.md §3.2
16
- * - architecture-validation.md §4.1
17
- */
18
- type Rule = unknown;
19
- declare const rule: Rule;
20
- export default rule;
@@ -1,72 +0,0 @@
1
- /**
2
- * @comity-dev/no-forbidden-deep-import
3
- *
4
- * Forbid deep imports into another `@comity/*` package's internals.
5
- *
6
- * Examples:
7
- * import { foo } from "@comity/pricing/src/foo" // ❌
8
- * import { foo } from "@comity/pricing/dist/foo.js" // ❌
9
- * import { foo } from "@comity/pricing/internal/x" // ❌
10
- * import { foo } from "@comity/pricing/lazy/x" // ❌
11
- * import { foo } from "@comity/pricing" // ✅
12
- * import { foo } from "@comity/pricing/exports/foo" // ✅
13
- *
14
- * Standard references:
15
- * - public-api.md §3.2
16
- * - architecture-validation.md §4.1
17
- */
18
- const FORBIDDEN_PATTERNS = [
19
- /^@comity\/[^/]+\/src\//,
20
- /^@comity\/[^/]+\/dist\//,
21
- /^@comity\/[^/]+\/internal/,
22
- /^@comity\/[^/]+\/lazy/,
23
- ];
24
- function isForbidden(specifier) {
25
- return FORBIDDEN_PATTERNS.some((re) => re.test(specifier));
26
- }
27
- const messages = {
28
- forbidden: "Deep imports into another package's internals are forbidden (public-api.md §3.2). Import only from declared public subpaths.",
29
- };
30
- const rule = {
31
- meta: {
32
- type: "problem",
33
- docs: {
34
- description: "Forbid deep imports into other `@comity/*` packages' internals (src/, dist/, internal/, lazy/).",
35
- category: "Comity Architecture",
36
- recommended: true,
37
- },
38
- schema: [],
39
- messages,
40
- },
41
- create(context) {
42
- return {
43
- ImportDeclaration(node) {
44
- const specifier = node.source.value;
45
- if (!specifier || !specifier.startsWith("@comity/"))
46
- return;
47
- if (isForbidden(specifier)) {
48
- context.report({ node, messageId: "forbidden" });
49
- }
50
- },
51
- ExportNamedDeclaration(node) {
52
- if (!node.source || !node.source.value)
53
- return;
54
- const specifier = node.source.value;
55
- if (!specifier.startsWith("@comity/"))
56
- return;
57
- if (isForbidden(specifier)) {
58
- context.report({ node, messageId: "forbidden" });
59
- }
60
- },
61
- ExportAllDeclaration(node) {
62
- const specifier = node.source.value;
63
- if (!specifier || !specifier.startsWith("@comity/"))
64
- return;
65
- if (isForbidden(specifier)) {
66
- context.report({ node, messageId: "forbidden" });
67
- }
68
- },
69
- };
70
- },
71
- };
72
- export default rule;
@@ -1,13 +0,0 @@
1
- /**
2
- * @comity-dev/no-forbidden-subpath-import
3
- *
4
- * Forbid exports to forbidden subpaths (`utils`, `helpers`, `shared`,
5
- * `internal`, `lazy`) per `public-api.md §3.2`.
6
- *
7
- * Standard references:
8
- * - public-api.md §3.2
9
- * - architecture-validation.md §4.2
10
- */
11
- type Rule = unknown;
12
- declare const rule: Rule;
13
- export default rule;
@@ -1,79 +0,0 @@
1
- /**
2
- * @comity-dev/no-forbidden-subpath-import
3
- *
4
- * Forbid exports to forbidden subpaths (`utils`, `helpers`, `shared`,
5
- * `internal`, `lazy`) per `public-api.md §3.2`.
6
- *
7
- * Standard references:
8
- * - public-api.md §3.2
9
- * - architecture-validation.md §4.2
10
- */
11
- const FORBIDDEN_SUBPATHS = new Set([
12
- "utils",
13
- "helpers",
14
- "shared",
15
- "internal",
16
- "lazy",
17
- ]);
18
- const messages = {
19
- forbiddenSubpath: "Exports subpath './{{segment}}' is forbidden (public-api.md §3.2).",
20
- };
21
- const rule = {
22
- meta: {
23
- type: "problem",
24
- docs: {
25
- description: "Forbid exports subpaths `utils`, `helpers`, `shared`, `internal`, `lazy`.",
26
- category: "Comity Architecture",
27
- recommended: true,
28
- },
29
- schema: [],
30
- messages,
31
- },
32
- create(context) {
33
- function isComityPackage(pkgName) {
34
- return pkgName.startsWith("@comity/");
35
- }
36
- return {
37
- ExportNamedDeclaration(node) {
38
- // No source = local export; not relevant
39
- if (!node.source || !node.source.value)
40
- return;
41
- const specifier = node.source.value;
42
- if (!specifier.startsWith("@comity/"))
43
- return;
44
- // Only check imports/exports OF other @comity packages
45
- const pkgMatch = specifier.match(/^@comity\/[^/]+/);
46
- if (!pkgMatch || !isComityPackage(pkgMatch[0]))
47
- return;
48
- // Subpath after the package name
49
- const subpath = specifier.slice(pkgMatch[0].length).replace(/^\//, "");
50
- const segment = subpath.split("/")[0];
51
- if (segment && FORBIDDEN_SUBPATHS.has(segment)) {
52
- context.report({
53
- node,
54
- messageId: "forbiddenSubpath",
55
- data: { segment },
56
- });
57
- }
58
- },
59
- ImportDeclaration(node) {
60
- const specifier = node.source.value;
61
- if (!specifier || !specifier.startsWith("@comity/"))
62
- return;
63
- const pkgMatch = specifier.match(/^@comity\/[^/]+/);
64
- if (!pkgMatch || !isComityPackage(pkgMatch[0]))
65
- return;
66
- const subpath = specifier.slice(pkgMatch[0].length).replace(/^\//, "");
67
- const segment = subpath.split("/")[0];
68
- if (segment && FORBIDDEN_SUBPATHS.has(segment)) {
69
- context.report({
70
- node,
71
- messageId: "forbiddenSubpath",
72
- data: { segment },
73
- });
74
- }
75
- },
76
- };
77
- },
78
- };
79
- export default rule;
@@ -1,27 +0,0 @@
1
- /**
2
- * @comity-dev/no-layer-violation
3
- *
4
- * Forbid imports that violate Comity's canonical layering policy.
5
- *
6
- * Disallowed edges (default):
7
- * - primitives → any @comity/*
8
- * - kernel → anything except primitives
9
- * - composition → anything except primitives, kernel
10
- * - core → adapters
11
- * - adapters → adapters (except integration-adapter → technology-adapter)
12
- *
13
- * Technology adapters MAY import the core contract they implement
14
- * (declared via `implements` metadata).
15
- *
16
- * Allowed Core-to-Core edges are gated by ADR-008; this rule does NOT
17
- * evaluate that register (it is enforced separately by the dependency
18
- * rules / orchestrator). Use this rule only to catch the obvious violations.
19
- *
20
- * Standard references:
21
- * - layering-policy.md §2 (canonical layering model)
22
- * - architecture-validation.md §7
23
- * - ADR-007 (adapter categories)
24
- */
25
- type Rule = unknown;
26
- declare const rule: Rule;
27
- export default rule;
@@ -1,154 +0,0 @@
1
- /**
2
- * @comity-dev/no-layer-violation
3
- *
4
- * Forbid imports that violate Comity's canonical layering policy.
5
- *
6
- * Disallowed edges (default):
7
- * - primitives → any @comity/*
8
- * - kernel → anything except primitives
9
- * - composition → anything except primitives, kernel
10
- * - core → adapters
11
- * - adapters → adapters (except integration-adapter → technology-adapter)
12
- *
13
- * Technology adapters MAY import the core contract they implement
14
- * (declared via `implements` metadata).
15
- *
16
- * Allowed Core-to-Core edges are gated by ADR-008; this rule does NOT
17
- * evaluate that register (it is enforced separately by the dependency
18
- * rules / orchestrator). Use this rule only to catch the obvious violations.
19
- *
20
- * Standard references:
21
- * - layering-policy.md §2 (canonical layering model)
22
- * - architecture-validation.md §7
23
- * - ADR-007 (adapter categories)
24
- */
25
- const messages = {
26
- forbidden: "This import violates Comity's layering policy (layering-policy.md §2).",
27
- };
28
- const LAYER_PROFILES = {
29
- primitives: { allowed: new Set() },
30
- kernel: { allowed: new Set(["primitives"]) },
31
- composition: { allowed: new Set(["primitives", "kernel"]) },
32
- core: { allowed: new Set(["primitives", "kernel", "core"]) },
33
- "technology-adapter": {
34
- allowed: new Set(["primitives", "kernel"]),
35
- },
36
- "integration-adapter": {
37
- allowed: new Set(["primitives", "kernel", "core", "technology-adapter"]),
38
- },
39
- };
40
- function classifyTarget(specifier) {
41
- const m = specifier.match(/^(@comity\/[^/]+)/);
42
- if (!m || !m[1])
43
- return null;
44
- return m[1];
45
- }
46
- function inferLayerFromFilename(filename) {
47
- if (!filename)
48
- return null;
49
- if (filename.includes("/primitives/"))
50
- return "primitives";
51
- if (filename.includes("/kernel/"))
52
- return "kernel";
53
- if (filename.includes("/composition/"))
54
- return "composition";
55
- if (filename.includes("/packages/")) {
56
- // Check for core packages
57
- const corePackages = [
58
- "http", "router", "html", "hydration", "seo", "content", "media", "search",
59
- "cache", "auth", "catalog", "customer", "inventory", "order", "payment",
60
- "pricing", "storefront", "taxonomy", "validation", "address", "geography",
61
- "identity", "organization", "acl", "auth-tokens", "graphql-builder",
62
- "graphql-client", "i18n", "sql", "storage"
63
- ];
64
- for (const pkg of corePackages) {
65
- if (filename.includes(`/packages/${pkg}/`))
66
- return "core";
67
- }
68
- // Check for technology adapters
69
- const adapterPackages = [
70
- "html-preact", "html-react", "hydration-preact", "hydration-react",
71
- "http-hono", "http-fetch", "router-path-to-regexp",
72
- "acl-casl", "auth-jose", "cache-kv", "cache-redis",
73
- "cli-commander", "graphql-client-fetch", "graphql-client-ws",
74
- "i18n-typesafe", "sql-kysely", "validation-zod"
75
- ];
76
- for (const pkg of adapterPackages) {
77
- if (filename.includes(`/packages/${pkg}/`))
78
- return "technology-adapter";
79
- }
80
- }
81
- return null;
82
- }
83
- const rule = {
84
- meta: {
85
- type: "problem",
86
- docs: {
87
- description: "Forbid imports that violate Comity's canonical layering policy.",
88
- category: "Comity Architecture",
89
- recommended: true,
90
- },
91
- schema: [
92
- {
93
- type: "object",
94
- properties: {
95
- sourceName: {
96
- type: "string",
97
- description: "Name of the source package (e.g., '@comity/pricing'). Required.",
98
- },
99
- classification: {
100
- type: "object",
101
- description: "Map of package name → layer. The plugin receives this via configuration; it MUST NOT hardcode layers.",
102
- },
103
- implements: {
104
- type: "object",
105
- description: "Map of package name → implemented core package (for technology-adapter packages).",
106
- },
107
- },
108
- required: ["sourceName", "classification"],
109
- additionalProperties: false,
110
- },
111
- ],
112
- messages,
113
- },
114
- create(context) {
115
- const opts = (context.options[0] ?? {});
116
- const classification = opts.classification ?? {};
117
- const implementsMap = opts.implements ?? {};
118
- const sourceName = opts.sourceName ?? null;
119
- // Fallback: infer source layer from filename if sourceName not provided
120
- let sourceLayer = sourceName ? classification[sourceName] : inferLayerFromFilename(context.filename ?? context.getFilename());
121
- if (!sourceLayer)
122
- return {};
123
- const profile = LAYER_PROFILES[sourceLayer];
124
- if (!profile)
125
- return {};
126
- // If sourceName not provided, we can't check implementsMap, so skip that allowance
127
- const effectiveSourceName = sourceName;
128
- return {
129
- ImportDeclaration(node) {
130
- const specifier = node.source.value;
131
- if (!specifier)
132
- return;
133
- const target = classifyTarget(specifier);
134
- if (!target)
135
- return;
136
- // Look up the target package's layer
137
- const targetLayer = classification[target];
138
- if (!targetLayer)
139
- return; // unknown package — outside this rule's scope
140
- // Allow technology-adapter to import its implemented core contract
141
- if (effectiveSourceName &&
142
- sourceLayer === "technology-adapter" &&
143
- targetLayer === "core" &&
144
- implementsMap[effectiveSourceName] === target) {
145
- return; // Allowed: adapter implements this core contract
146
- }
147
- if (!profile.allowed.has(targetLayer)) {
148
- context.report({ node, messageId: "forbidden" });
149
- }
150
- },
151
- };
152
- },
153
- };
154
- export default rule;
@@ -1,18 +0,0 @@
1
- /**
2
- * @comity-dev/no-render-core-http-runtime
3
- *
4
- * Forbid Rendering Core Modules (html, hydration, search, seo, content)
5
- * from importing HTTP runtime implementations (http-hono, http-fetch,
6
- * html-preact, html-react, hydration-preact, hydration-react).
7
- *
8
- * The rule is scoped to only apply to files within the declared Rendering
9
- * Core packages. Technology adapters and client entry points are permitted
10
- * to import their respective runtimes.
11
- *
12
- * Standard references:
13
- * - layering-policy.md §8 (rendering core MUST NOT depend on HTTP runtime)
14
- * - architecture-validation.md §4.3
15
- */
16
- type Rule = unknown;
17
- declare const rule: Rule;
18
- export default rule;
@@ -1,100 +0,0 @@
1
- /**
2
- * @comity-dev/no-render-core-http-runtime
3
- *
4
- * Forbid Rendering Core Modules (html, hydration, search, seo, content)
5
- * from importing HTTP runtime implementations (http-hono, http-fetch,
6
- * html-preact, html-react, hydration-preact, hydration-react).
7
- *
8
- * The rule is scoped to only apply to files within the declared Rendering
9
- * Core packages. Technology adapters and client entry points are permitted
10
- * to import their respective runtimes.
11
- *
12
- * Standard references:
13
- * - layering-policy.md §8 (rendering core MUST NOT depend on HTTP runtime)
14
- * - architecture-validation.md §4.3
15
- */
16
- const RENDERING_CORE_PACKAGES = new Set([
17
- "@comity/html",
18
- "@comity/hydration",
19
- "@comity/search",
20
- "@comity/seo",
21
- "@comity/content",
22
- ]);
23
- const FORBIDDEN_RUNTIME_PACKAGES = new Set([
24
- "@comity/http-hono",
25
- "@comity/http-fetch",
26
- "@comity/html-preact",
27
- "@comity/html-react",
28
- "@comity/hydration-preact",
29
- "@comity/hydration-react",
30
- ]);
31
- const messages = {
32
- forbiddenRuntime: "Rendering Core Module MUST NOT import HTTP/Rendering runtime '{{target}}' (layering-policy.md §8).",
33
- };
34
- function isInRenderingCorePackage(filename) {
35
- return Array.from(RENDERING_CORE_PACKAGES).some((pkg) => filename.includes(`/packages/${pkg.replace("@comity/", "")}/`));
36
- }
37
- const rule = {
38
- meta: {
39
- type: "problem",
40
- docs: {
41
- description: "Forbid Rendering Core Modules from importing HTTP / Rendering runtimes.",
42
- category: "Comity Architecture",
43
- recommended: true,
44
- },
45
- schema: [
46
- {
47
- type: "object",
48
- properties: {
49
- sourceName: {
50
- type: "string",
51
- description: "Name of the source package (e.g., '@comity/html'). Used to scope the rule.",
52
- },
53
- },
54
- additionalProperties: false,
55
- },
56
- ],
57
- messages,
58
- },
59
- create(context) {
60
- const opts = (context.options[0] ?? {});
61
- const sourceName = opts.sourceName ?? null;
62
- // If no sourceName provided, fall back to filename heuristic
63
- const inRenderingCore = sourceName
64
- ? RENDERING_CORE_PACKAGES.has(sourceName)
65
- : isInRenderingCorePackage(context.filename ?? context.getFilename());
66
- // Disable this rule outside rendering core packages
67
- if (!inRenderingCore) {
68
- return {};
69
- }
70
- function checkImport(specifier, node) {
71
- if (!specifier.startsWith("@comity/"))
72
- return;
73
- if (FORBIDDEN_RUNTIME_PACKAGES.has(specifier)) {
74
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
75
- context.report({
76
- node: node,
77
- messageId: "forbiddenRuntime",
78
- data: { target: specifier },
79
- });
80
- }
81
- }
82
- return {
83
- ImportDeclaration(node) {
84
- checkImport(node.source.value, node);
85
- },
86
- ImportExpression(node) {
87
- if (node.source.type === "Literal" &&
88
- typeof node.source.value === "string") {
89
- checkImport(node.source.value, node);
90
- }
91
- },
92
- ExportNamedDeclaration(node) {
93
- if (node.source && node.source.value) {
94
- checkImport(node.source.value, node);
95
- }
96
- },
97
- };
98
- },
99
- };
100
- export default rule;