@webpresso/agent-config 0.4.2 → 0.4.4

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.
@@ -0,0 +1,35 @@
1
+ declare const plugin: {
2
+ meta: {
3
+ name: string;
4
+ };
5
+ rules: {
6
+ "no-weak-assertions": {
7
+ create(context: any): {
8
+ CallExpression(node: any): void;
9
+ };
10
+ };
11
+ "no-bare-spy-assertions": {
12
+ create(context: any): {
13
+ CallExpression(node: any): void;
14
+ };
15
+ };
16
+ "no-internal-mocks": {
17
+ create(context: any): {
18
+ CallExpression(node: any): void;
19
+ };
20
+ };
21
+ "no-real-timers-in-tests": {
22
+ create(context: any): {
23
+ NewExpression(node: any): void;
24
+ };
25
+ };
26
+ "no-cold-dynamic-import": {
27
+ create(context: any): {
28
+ CallExpression(node: any): void;
29
+ "CallExpression:exit"(node: any): void;
30
+ ImportExpression(node: any): void;
31
+ };
32
+ };
33
+ };
34
+ };
35
+ export default plugin;
@@ -0,0 +1,186 @@
1
+ // @ts-nocheck
2
+ // Webpresso testing quality rules — replaces GritQL patterns:
3
+ // - no-weak-assertions (no-tobefalsy, no-tobedefined)
4
+ // - no-bare-spy-assertions
5
+ // - no-internal-mocks
6
+ // - no-real-timers-in-tests
7
+ const noWeakAssertions = {
8
+ create(context) {
9
+ return {
10
+ CallExpression(node) {
11
+ if (node.callee.type !== "MemberExpression" || node.callee.property.type !== "Identifier") {
12
+ return;
13
+ }
14
+ const method = node.callee.property.name;
15
+ const weak = ["toBeTruthy", "toBeFalsy", "toBeDefined", "toBeUndefined", "toBeTypeOf"];
16
+ if (!weak.includes(method))
17
+ return;
18
+ if (node.arguments.length > 0 && method !== "toBeTypeOf")
19
+ return;
20
+ const messages = {
21
+ toBeTruthy: "Weak assertion: toBeTruthy() matches too many values. Use toBe(true) for strict equality.",
22
+ toBeFalsy: "Weak assertion: toBeFalsy() matches too many values. Use toBe(false) for strict equality.",
23
+ toBeDefined: "Weak assertion: toBeDefined() allows equivalent mutants. Use toBe(expectedValue) or toEqual(expectedValue).",
24
+ toBeUndefined: "Weak assertion: toBeUndefined() allows equivalent mutants. Use toBe(undefined) explicitly.",
25
+ toBeTypeOf: "Weak assertion: toBeTypeOf() only checks the type, not the value. Use toBe(expectedValue) or toEqual(expectedValue).",
26
+ };
27
+ context.report({ node: node.callee.property, message: messages[method] });
28
+ },
29
+ };
30
+ },
31
+ };
32
+ const noBareSpy = {
33
+ create(context) {
34
+ return {
35
+ CallExpression(node) {
36
+ if (node.callee.type !== "MemberExpression" ||
37
+ node.callee.property.type !== "Identifier" ||
38
+ node.callee.property.name !== "toHaveBeenCalled" ||
39
+ node.arguments.length > 0) {
40
+ return;
41
+ }
42
+ // Allow .not.toHaveBeenCalled()
43
+ const obj = node.callee.object;
44
+ if (obj.type === "MemberExpression" &&
45
+ obj.property.type === "Identifier" &&
46
+ obj.property.name === "not") {
47
+ return;
48
+ }
49
+ context.report({
50
+ node: node.callee.property,
51
+ message: "Weak spy assertion: toHaveBeenCalled() without arguments. Use toHaveBeenCalledWith(expected), toHaveBeenNthCalledWith(n, expected), or toHaveBeenCalledTimes(count).",
52
+ });
53
+ },
54
+ };
55
+ },
56
+ };
57
+ const noInternalMocks = {
58
+ create(context) {
59
+ return {
60
+ CallExpression(node) {
61
+ if (node.callee.type !== "MemberExpression" ||
62
+ node.callee.object.type !== "Identifier" ||
63
+ node.callee.object.name !== "vi" ||
64
+ node.callee.property.type !== "Identifier" ||
65
+ node.callee.property.name !== "mock") {
66
+ return;
67
+ }
68
+ const arg = node.arguments[0];
69
+ if (!arg || arg.type !== "Literal" || typeof arg.value !== "string")
70
+ return;
71
+ if (arg.value.includes("@webpresso")) {
72
+ // Allow mocking I/O boundary modules (filesystem loaders/writers)
73
+ // These are legitimate mock targets even in unit tests
74
+ const ioBoundaryPrefixes = [
75
+ "@webpresso/schema/loaders/", // YAML filesystem loaders & writers
76
+ "@webpresso/scripts/", // Repo-wide scripts & dev orchestration I/O boundaries
77
+ ];
78
+ if (ioBoundaryPrefixes.some((prefix) => arg.value.startsWith(prefix)))
79
+ return;
80
+ context.report({
81
+ node: arg,
82
+ message: "Mocking internal @webpresso/* package. Use real dependencies (PGlite for DB, real services for logic). Convert to .integration.test.ts if needed.",
83
+ });
84
+ }
85
+ },
86
+ };
87
+ },
88
+ };
89
+ const noRealTimers = {
90
+ create(context) {
91
+ return {
92
+ NewExpression(node) {
93
+ if (node.callee.type !== "Identifier" || node.callee.name !== "Promise") {
94
+ return;
95
+ }
96
+ const arg = node.arguments[0];
97
+ if (!arg || (arg.type !== "ArrowFunctionExpression" && arg.type !== "FunctionExpression")) {
98
+ return;
99
+ }
100
+ const body = arg.body.type === "CallExpression"
101
+ ? arg.body
102
+ : arg.body.type === "BlockStatement" &&
103
+ arg.body.body.length === 1 &&
104
+ arg.body.body[0].type === "ExpressionStatement"
105
+ ? arg.body.body[0].expression
106
+ : null;
107
+ if (body &&
108
+ body.type === "CallExpression" &&
109
+ body.callee.type === "Identifier" &&
110
+ body.callee.name === "setTimeout") {
111
+ context.report({
112
+ node,
113
+ message: "setTimeout inside Promise constructor will hang with fake timers. Use await Promise.resolve() for microtask delays, or vi.advanceTimersByTimeAsync() for time-based delays.",
114
+ });
115
+ }
116
+ },
117
+ };
118
+ },
119
+ };
120
+ function isTestCall(node) {
121
+ const { callee } = node;
122
+ if (callee.type === "Identifier")
123
+ return callee.name === "it" || callee.name === "test";
124
+ return false;
125
+ }
126
+ function isBeforeAllCall(node) {
127
+ const { callee } = node;
128
+ return callee.type === "Identifier" && callee.name === "beforeAll";
129
+ }
130
+ const noColdDynamicImport = {
131
+ create(context) {
132
+ // Track depth inside it()/test() callback bodies via the linter's own traversal.
133
+ // Using ':exit' to decrement so we never need to walk the subtree manually
134
+ // (manual walks hit circular parent back-references in oxlint's proxy AST).
135
+ let testDepth = 0;
136
+ let beforeAllDepth = 0;
137
+ const prewarmedModules = new Set();
138
+ return {
139
+ CallExpression(node) {
140
+ if (isTestCall(node))
141
+ testDepth++;
142
+ if (isBeforeAllCall(node))
143
+ beforeAllDepth++;
144
+ },
145
+ "CallExpression:exit"(node) {
146
+ if (isTestCall(node))
147
+ testDepth--;
148
+ if (isBeforeAllCall(node))
149
+ beforeAllDepth--;
150
+ },
151
+ ImportExpression(node) {
152
+ const src = node.source;
153
+ if (src.type !== "Literal" || typeof src.value !== "string")
154
+ return;
155
+ if (!src.value.startsWith("@webpresso/") && !src.value.startsWith("#"))
156
+ return;
157
+ // Track modules pre-warmed in beforeAll
158
+ if (beforeAllDepth > 0) {
159
+ prewarmedModules.add(src.value);
160
+ return;
161
+ }
162
+ if (testDepth === 0)
163
+ return;
164
+ // Skip if module was pre-warmed via beforeAll
165
+ if (prewarmedModules.has(src.value))
166
+ return;
167
+ context.report({
168
+ node: src,
169
+ message: `Dynamic @webpresso/* import inside test body causes cold-start timeouts under parallel execution. ` +
170
+ `Add \`beforeAll(() => import('${src.value}'))\` at the top of the file to pre-warm the module cache.`,
171
+ });
172
+ },
173
+ };
174
+ },
175
+ };
176
+ const plugin = {
177
+ meta: { name: "webpresso-testing" },
178
+ rules: {
179
+ "no-weak-assertions": noWeakAssertions,
180
+ "no-bare-spy-assertions": noBareSpy,
181
+ "no-internal-mocks": noInternalMocks,
182
+ "no-real-timers-in-tests": noRealTimers,
183
+ "no-cold-dynamic-import": noColdDynamicImport,
184
+ },
185
+ };
186
+ export default plugin;
@@ -0,0 +1,35 @@
1
+ export declare function resolveFileTierContext(filename: any): {
2
+ kind: string;
3
+ packageName: any;
4
+ tier: number;
5
+ } | {
6
+ kind: string;
7
+ } | null;
8
+ export declare function resolveImportTierContext(source: any): {
9
+ kind: string;
10
+ packageName: any;
11
+ tier: number;
12
+ } | {
13
+ kind: string;
14
+ } | null;
15
+ declare const plugin: {
16
+ meta: {
17
+ name: string;
18
+ };
19
+ rules: {
20
+ "no-higher-tier-imports": {
21
+ create(context: any): {
22
+ ImportDeclaration?: undefined;
23
+ ExportNamedDeclaration?: undefined;
24
+ ExportAllDeclaration?: undefined;
25
+ ImportExpression?: undefined;
26
+ } | {
27
+ ImportDeclaration(node: any): void;
28
+ ExportNamedDeclaration(node: any): void;
29
+ ExportAllDeclaration(node: any): void;
30
+ ImportExpression(node: any): void;
31
+ };
32
+ };
33
+ };
34
+ };
35
+ export default plugin;
@@ -0,0 +1,133 @@
1
+ // @ts-nocheck
2
+ import { pathToFileURL } from "node:url";
3
+ import { resolve } from "node:path";
4
+ const FALLBACK_PACKAGE_TIERS = {
5
+ utils: 0,
6
+ types: 0,
7
+ database: 1,
8
+ ui: 1,
9
+ "app-core": 2,
10
+ "cli-wp": 3,
11
+ };
12
+ async function loadPackageTiers() {
13
+ const packageBoundaryModuleUrl = pathToFileURL(resolve(process.cwd(), "package-boundaries.js"));
14
+ try {
15
+ const packageBoundaryModule = await import(packageBoundaryModuleUrl.href);
16
+ return packageBoundaryModule.PACKAGE_TIERS ?? FALLBACK_PACKAGE_TIERS;
17
+ }
18
+ catch {
19
+ return FALLBACK_PACKAGE_TIERS;
20
+ }
21
+ }
22
+ const PACKAGE_TIERS = await loadPackageTiers();
23
+ function normalizeFilename(filename) {
24
+ return typeof filename === "string" ? filename.replaceAll("\\", "/") : "";
25
+ }
26
+ function getFilename(context) {
27
+ if (typeof context.getFilename === "function") {
28
+ return normalizeFilename(context.getFilename());
29
+ }
30
+ return normalizeFilename(context.filename);
31
+ }
32
+ function getPathSegments(filename) {
33
+ return normalizeFilename(filename).split("/").filter(Boolean);
34
+ }
35
+ function getWorkspacePackageContext(packageName) {
36
+ const tier = PACKAGE_TIERS[packageName];
37
+ if (typeof tier !== "number") {
38
+ return null;
39
+ }
40
+ return {
41
+ kind: "package",
42
+ packageName,
43
+ tier,
44
+ };
45
+ }
46
+ export function resolveFileTierContext(filename) {
47
+ const segments = getPathSegments(filename);
48
+ if (segments.includes("infra")) {
49
+ return { kind: "infra" };
50
+ }
51
+ if (segments.includes("packages-public")) {
52
+ return { kind: "packages-public" };
53
+ }
54
+ const packagesIndex = segments.indexOf("packages");
55
+ if (packagesIndex !== -1 && packagesIndex + 2 < segments.length) {
56
+ return getWorkspacePackageContext(segments[packagesIndex + 2]);
57
+ }
58
+ const appsIndex = segments.indexOf("apps");
59
+ if (appsIndex !== -1) {
60
+ const directAppContext = getWorkspacePackageContext(segments[appsIndex + 1]);
61
+ if (directAppContext) {
62
+ return directAppContext;
63
+ }
64
+ if (appsIndex + 2 < segments.length) {
65
+ return getWorkspacePackageContext(segments[appsIndex + 2]);
66
+ }
67
+ }
68
+ return null;
69
+ }
70
+ export function resolveImportTierContext(source) {
71
+ if (typeof source !== "string") {
72
+ return null;
73
+ }
74
+ if (source.startsWith("packages-public/")) {
75
+ return { kind: "packages-public" };
76
+ }
77
+ if (!source.startsWith("@webpresso/")) {
78
+ return null;
79
+ }
80
+ const packageName = source.slice("@webpresso/".length).split("/")[0];
81
+ return getWorkspacePackageContext(packageName);
82
+ }
83
+ function reportTierViolation(context, node, fromContext, toContext) {
84
+ context.report({
85
+ node,
86
+ message: `Tier boundary violation: "${fromContext.packageName}" (tier ${fromContext.tier}) must not import higher-tier package "${toContext.packageName}" (tier ${toContext.tier}).`,
87
+ });
88
+ }
89
+ function checkStaticModuleSource(node, callback) {
90
+ if (!node.source)
91
+ return;
92
+ callback(node.source.value, node.source);
93
+ }
94
+ const noHigherTierImports = {
95
+ create(context) {
96
+ const fromContext = resolveFileTierContext(getFilename(context));
97
+ if (!fromContext || fromContext.kind !== "package") {
98
+ return {};
99
+ }
100
+ function checkImport(source, node) {
101
+ const toContext = resolveImportTierContext(source);
102
+ if (!toContext || toContext.kind !== "package") {
103
+ return;
104
+ }
105
+ if (toContext.tier > fromContext.tier) {
106
+ reportTierViolation(context, node, fromContext, toContext);
107
+ }
108
+ }
109
+ return {
110
+ ImportDeclaration(node) {
111
+ checkImport(node.source.value, node.source);
112
+ },
113
+ ExportNamedDeclaration(node) {
114
+ checkStaticModuleSource(node, checkImport);
115
+ },
116
+ ExportAllDeclaration(node) {
117
+ checkStaticModuleSource(node, checkImport);
118
+ },
119
+ ImportExpression(node) {
120
+ if (node.source.type !== "Literal")
121
+ return;
122
+ checkImport(node.source.value, node.source);
123
+ },
124
+ };
125
+ },
126
+ };
127
+ const plugin = {
128
+ meta: { name: "webpresso-tier-boundaries" },
129
+ rules: {
130
+ "no-higher-tier-imports": noHigherTierImports,
131
+ },
132
+ };
133
+ export default plugin;
@@ -44,9 +44,20 @@ export declare const baseConfig: {
44
44
  incrementalFile: string;
45
45
  };
46
46
  /**
47
- * Extends baseConfig with TypeScript checker defaults.
47
+ * Extends baseConfig for TypeScript packages.
48
48
  * Use this in any TypeScript package instead of baseConfig directly.
49
49
  *
50
+ * Does NOT wire `@stryker-mutator/typescript-checker`: that plugin depends on
51
+ * classic TypeScript compiler-API symbols (`ts.sys`,
52
+ * `ts.createSolutionBuilderWithWatch`, `ts.createSolutionBuilderWithWatchHost`,
53
+ * `ts.createEmitAndSemanticDiagnosticsBuilderProgram`,
54
+ * `ts.parseConfigFileTextToJson`, `ts.formatDiagnostics`) that are all
55
+ * `undefined` on TypeScript 7.0.2, so wiring it breaks mutation runs on TS7
56
+ * with a hard failure rather than classifying compile errors. Consumers who
57
+ * still want mutant type-checking on TS7 must add a consumer-side pnpm
58
+ * override aliasing the plugin's `typescript` import to
59
+ * `@typescript/typescript6` themselves.
60
+ *
50
61
  * @example
51
62
  * import { typescriptBaseConfig } from '@webpresso/agent-config/stryker'
52
63
  *
@@ -57,6 +68,7 @@ export declare const baseConfig: {
57
68
  export declare const typescriptBaseConfig: {
58
69
  packageManager: string;
59
70
  testRunner: string;
71
+ plugins: string[];
60
72
  ignorePatterns: string[];
61
73
  mutate: string[];
62
74
  concurrency: number;
@@ -83,8 +95,6 @@ export declare const typescriptBaseConfig: {
83
95
  };
84
96
  incremental: boolean;
85
97
  incrementalFile: string;
86
- plugins: string[];
87
- checkers: string[];
88
98
  tsconfigFile: string;
89
99
  };
90
100
  /**
@@ -101,6 +111,7 @@ export declare const typescriptBaseConfig: {
101
111
  export declare const typescriptWorkersBaseConfig: {
102
112
  packageManager: string;
103
113
  testRunner: string;
114
+ plugins: string[];
104
115
  ignorePatterns: string[];
105
116
  mutate: string[];
106
117
  concurrency: number;
@@ -124,8 +135,6 @@ export declare const typescriptWorkersBaseConfig: {
124
135
  };
125
136
  incremental: boolean;
126
137
  incrementalFile: string;
127
- plugins: string[];
128
- checkers: string[];
129
138
  tsconfigFile: string;
130
139
  vitest: {
131
140
  configFile: string;
@@ -93,9 +93,20 @@ export const baseConfig = {
93
93
  incrementalFile: "reports/stryker-incremental.json",
94
94
  };
95
95
  /**
96
- * Extends baseConfig with TypeScript checker defaults.
96
+ * Extends baseConfig for TypeScript packages.
97
97
  * Use this in any TypeScript package instead of baseConfig directly.
98
98
  *
99
+ * Does NOT wire `@stryker-mutator/typescript-checker`: that plugin depends on
100
+ * classic TypeScript compiler-API symbols (`ts.sys`,
101
+ * `ts.createSolutionBuilderWithWatch`, `ts.createSolutionBuilderWithWatchHost`,
102
+ * `ts.createEmitAndSemanticDiagnosticsBuilderProgram`,
103
+ * `ts.parseConfigFileTextToJson`, `ts.formatDiagnostics`) that are all
104
+ * `undefined` on TypeScript 7.0.2, so wiring it breaks mutation runs on TS7
105
+ * with a hard failure rather than classifying compile errors. Consumers who
106
+ * still want mutant type-checking on TS7 must add a consumer-side pnpm
107
+ * override aliasing the plugin's `typescript` import to
108
+ * `@typescript/typescript6` themselves.
109
+ *
99
110
  * @example
100
111
  * import { typescriptBaseConfig } from '@webpresso/agent-config/stryker'
101
112
  *
@@ -105,8 +116,6 @@ export const baseConfig = {
105
116
  */
106
117
  export const typescriptBaseConfig = {
107
118
  ...baseConfig,
108
- plugins: [...baseConfig.plugins, "@stryker-mutator/typescript-checker"],
109
- checkers: ["typescript"],
110
119
  tsconfigFile: "tsconfig.json",
111
120
  };
112
121
  /**
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@webpresso/agent-config",
3
- "version": "0.4.2",
3
+ "version": "0.4.4",
4
4
  "private": false,
5
- "description": "Shared vitest, tsconfig, stryker, and workers-test config for webpresso consumers. Binary-free — pure test/config tooling.",
5
+ "description": "Shared vitest, tsconfig, stryker, workers-test, and oxlint policy plugins for webpresso consumers. Binary-free — pure test/config tooling.",
6
6
  "homepage": "https://github.com/webpresso/agent-kit#readme",
7
7
  "bugs": {
8
8
  "url": "https://github.com/webpresso/agent-kit/issues"
@@ -45,6 +45,72 @@
45
45
  "default": "./dist/esm/e2e/index.js"
46
46
  }
47
47
  },
48
+ "./oxlint": {
49
+ "types": "./dist/esm/oxlint/index.d.ts",
50
+ "import": "./dist/esm/oxlint/index.js",
51
+ "require": "./dist/esm/oxlint/index.js",
52
+ "default": "./dist/esm/oxlint/index.js"
53
+ },
54
+ "./oxlint/basenames": {
55
+ "types": "./dist/esm/oxlint/basenames.d.ts",
56
+ "import": "./dist/esm/oxlint/basenames.js",
57
+ "require": "./dist/esm/oxlint/basenames.js",
58
+ "default": "./dist/esm/oxlint/basenames.js"
59
+ },
60
+ "./oxlint/code-safety": {
61
+ "types": "./dist/esm/oxlint/code-safety.d.ts",
62
+ "import": "./dist/esm/oxlint/code-safety.js",
63
+ "require": "./dist/esm/oxlint/code-safety.js",
64
+ "default": "./dist/esm/oxlint/code-safety.js"
65
+ },
66
+ "./oxlint/foundation-purity": {
67
+ "types": "./dist/esm/oxlint/foundation-purity.d.ts",
68
+ "import": "./dist/esm/oxlint/foundation-purity.js",
69
+ "require": "./dist/esm/oxlint/foundation-purity.js",
70
+ "default": "./dist/esm/oxlint/foundation-purity.js"
71
+ },
72
+ "./oxlint/graphql-conventions": {
73
+ "types": "./dist/esm/oxlint/graphql-conventions.d.ts",
74
+ "import": "./dist/esm/oxlint/graphql-conventions.js",
75
+ "require": "./dist/esm/oxlint/graphql-conventions.js",
76
+ "default": "./dist/esm/oxlint/graphql-conventions.js"
77
+ },
78
+ "./oxlint/import-hygiene": {
79
+ "types": "./dist/esm/oxlint/import-hygiene.d.ts",
80
+ "import": "./dist/esm/oxlint/import-hygiene.js",
81
+ "require": "./dist/esm/oxlint/import-hygiene.js",
82
+ "default": "./dist/esm/oxlint/import-hygiene.js"
83
+ },
84
+ "./oxlint/monorepo-paths": {
85
+ "types": "./dist/esm/oxlint/monorepo-paths.d.ts",
86
+ "import": "./dist/esm/oxlint/monorepo-paths.js",
87
+ "require": "./dist/esm/oxlint/monorepo-paths.js",
88
+ "default": "./dist/esm/oxlint/monorepo-paths.js"
89
+ },
90
+ "./oxlint/path-roles": {
91
+ "types": "./dist/esm/oxlint/path-roles.d.ts",
92
+ "import": "./dist/esm/oxlint/path-roles.js",
93
+ "require": "./dist/esm/oxlint/path-roles.js",
94
+ "default": "./dist/esm/oxlint/path-roles.js"
95
+ },
96
+ "./oxlint/query-patterns": {
97
+ "types": "./dist/esm/oxlint/query-patterns.d.ts",
98
+ "import": "./dist/esm/oxlint/query-patterns.js",
99
+ "require": "./dist/esm/oxlint/query-patterns.js",
100
+ "default": "./dist/esm/oxlint/query-patterns.js"
101
+ },
102
+ "./oxlint/testing-quality": {
103
+ "types": "./dist/esm/oxlint/testing-quality.d.ts",
104
+ "import": "./dist/esm/oxlint/testing-quality.js",
105
+ "require": "./dist/esm/oxlint/testing-quality.js",
106
+ "default": "./dist/esm/oxlint/testing-quality.js"
107
+ },
108
+ "./oxlint/tier-boundaries": {
109
+ "types": "./dist/esm/oxlint/tier-boundaries.d.ts",
110
+ "import": "./dist/esm/oxlint/tier-boundaries.js",
111
+ "require": "./dist/esm/oxlint/tier-boundaries.js",
112
+ "default": "./dist/esm/oxlint/tier-boundaries.js"
113
+ },
48
114
  "./playwright/quality-scaffold": {
49
115
  "import": {
50
116
  "types": "./dist/esm/playwright/quality-scaffold.d.ts",
@@ -177,7 +243,7 @@
177
243
  "registry": "https://registry.npmjs.org/"
178
244
  },
179
245
  "scripts": {
180
- "build": "tshy && bun scripts/normalize-tsconfig-json-exports.ts",
246
+ "build": "tshy && bun scripts/normalize-tsconfig-json-exports.ts && bun scripts/normalize-oxlint-exports.ts",
181
247
  "prepublishOnly": "vp run build",
182
248
  "typecheck": "tsc --noEmit",
183
249
  "lint:pkg": "publint && attw --pack . --profile esm-only"
@@ -234,6 +300,17 @@
234
300
  "./deploy": "./src/deploy/index.ts",
235
301
  "./dev": "./src/dev/index.ts",
236
302
  "./e2e": "./src/e2e/index.ts",
303
+ "./oxlint": "./src/oxlint/index.ts",
304
+ "./oxlint/basenames": "./src/oxlint/basenames.ts",
305
+ "./oxlint/code-safety": "./src/oxlint/code-safety.ts",
306
+ "./oxlint/foundation-purity": "./src/oxlint/foundation-purity.ts",
307
+ "./oxlint/graphql-conventions": "./src/oxlint/graphql-conventions.ts",
308
+ "./oxlint/import-hygiene": "./src/oxlint/import-hygiene.ts",
309
+ "./oxlint/monorepo-paths": "./src/oxlint/monorepo-paths.ts",
310
+ "./oxlint/path-roles": "./src/oxlint/path-roles.ts",
311
+ "./oxlint/query-patterns": "./src/oxlint/query-patterns.ts",
312
+ "./oxlint/testing-quality": "./src/oxlint/testing-quality.ts",
313
+ "./oxlint/tier-boundaries": "./src/oxlint/tier-boundaries.ts",
237
314
  "./playwright/quality-scaffold": "./src/playwright/quality-scaffold.ts",
238
315
  "./process": "./src/process/index.ts",
239
316
  "./repo-root": "./src/repo-root/index.ts",