@collective-intelligence/lint 0.0.0-stage → 0.1.19

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/README.md CHANGED
@@ -1,3 +1,172 @@
1
- # Temporary Holding Version
1
+ # @collective-intelligence/lint
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ The Collective Intelligence lint rules, as one Oxlint plugin. Every CI lint
4
+ rule lives in this package. The plugin is named `ci-solution`, so each rule is
5
+ enabled as `ci-solution/<rule>`.
6
+
7
+ ## Install
8
+
9
+ ```sh
10
+ bun add -d oxlint @collective-intelligence/lint
11
+ ```
12
+
13
+ The plugin needs `oxlint` 1.82.0 or later.
14
+
15
+ ## Use
16
+
17
+ ### All rules
18
+
19
+ Extend the `recommended` config in `oxlint.config.ts`. It loads the plugin and
20
+ enables every rule as an error:
21
+
22
+ ```ts
23
+ import { recommended } from "@collective-intelligence/lint"
24
+ import { defineConfig } from "oxlint"
25
+
26
+ export default defineConfig({
27
+ extends: [recommended]
28
+ })
29
+ ```
30
+
31
+ To change one rule, set it after the extend:
32
+
33
+ ```ts
34
+ export default defineConfig({
35
+ extends: [recommended],
36
+ rules: { "ci-solution/subpath-index": "warn" }
37
+ })
38
+ ```
39
+
40
+ ### Selected rules
41
+
42
+ Load the plugin and enable only the rules you want:
43
+
44
+ ```ts
45
+ import { defineConfig } from "oxlint"
46
+
47
+ export default defineConfig({
48
+ jsPlugins: ["@collective-intelligence/lint"],
49
+ rules: {
50
+ "ci-solution/package-entrypoint": "error",
51
+ "ci-solution/subpath-imports": "error"
52
+ }
53
+ })
54
+ ```
55
+
56
+ A JSON config works the same way:
57
+
58
+ ```json
59
+ {
60
+ "jsPlugins": ["@collective-intelligence/lint"],
61
+ "rules": { "ci-solution/package-entrypoint": "error" }
62
+ }
63
+ ```
64
+
65
+ The `RuleName` type lists every rule name.
66
+
67
+ ## Rules
68
+
69
+ | Rule | Enforces | Group |
70
+ | -------------------------------- | ------------------------------------------------------------------------------------------ | ------------------ |
71
+ | `ci-solution/package-entrypoint` | An import of another package targets an entrypoint declared in its `package.json` exports. | Package boundaries |
72
+ | `ci-solution/subpath-imports` | Imports between subpath folders follow the allowed direction. | Solution layout |
73
+ | `ci-solution/subpath-entrypoint` | An import from another subpath folder targets the folder or its index file. | Solution layout |
74
+ | `ci-solution/subpath-index` | Every subpath folder has an `index.ts` or `index.tsx`. | Solution layout |
75
+
76
+ ### Package boundaries
77
+
78
+ A workspace holds packages that import each other. These rules keep each
79
+ package behind its declared public entrypoints.
80
+
81
+ #### `package-entrypoint`
82
+
83
+ Import another package through its root (`@scope/pkg`) or a subpath listed in
84
+ its `package.json` `exports` (`@scope/pkg/views`). `*` patterns and `null`
85
+ exclusions follow Node resolution. The rule finds the package through
86
+ `node_modules` from the importing file, so workspace packages must be linked by
87
+ the package manager.
88
+
89
+ ```ts
90
+ import { invoiceTotal } from "@acme/billing" // valid
91
+ import { invoiceTable } from "@acme/billing/views" // valid when "./views" is exported
92
+ import { rate } from "@acme/billing/src/domain/rates" // reported
93
+ import { rate } from "../../billing/src/domain/rates" // reported: relative path into another package
94
+ ```
95
+
96
+ A package without an `exports` field accepts every subpath except `src/`.
97
+ Packages that cannot be found are not checked.
98
+
99
+ ### Solution layout
100
+
101
+ A solution package can split its sources into subpath folders:
102
+
103
+ ```text
104
+ src/
105
+ ci/ Collective Intelligence entity and capability declarations
106
+ domain/ domain model, free of runtime concerns
107
+ api/ API contracts
108
+ server/ server handlers and runtime wiring
109
+ headless/ UI state without rendering
110
+ views/ rendered UI
111
+ ```
112
+
113
+ These rules keep those folders apart.
114
+
115
+ #### `subpath-imports`
116
+
117
+ | Folder | Must not import |
118
+ | ----------- | --------------------------------------- |
119
+ | `domain/` | `server/`, `views/`, `headless/`, `ci/` |
120
+ | `ci/` | `views/`, `headless/` |
121
+ | `headless/` | `server/` |
122
+ | `views/` | `server/` |
123
+ | `api/` | no limit |
124
+ | `server/` | no limit |
125
+
126
+ The rule checks relative imports and imports through the package's own name
127
+ (`@acme/billing/server` from `src/views/`).
128
+
129
+ #### `subpath-entrypoint`
130
+
131
+ An import from another subpath folder targets the folder or its index file.
132
+ Imports inside one subpath folder are free.
133
+
134
+ ```ts
135
+ // src/views/table.ts
136
+ import { invoiceTotal } from "../domain" // valid
137
+ import { invoiceTotal } from "../domain/index.ts" // valid
138
+ import { rate } from "../domain/rates" // reported
139
+ ```
140
+
141
+ #### `subpath-index`
142
+
143
+ Every subpath folder under `src/` has an `index.ts` or `index.tsx` entrypoint.
144
+ The rule reports each file in a folder that has neither.
145
+
146
+ ## Adding a rule
147
+
148
+ Each rule is a plain Oxlint rule in its own folder, `src/rules/<rule>/`:
149
+
150
+ 1. Write `src/rules/<rule>/<rule>.ts`. Export a `LintRule` (`src/rule.ts`)
151
+ with:
152
+ - `group`: the rule group in the table above (add a group to `RuleGroup`
153
+ when no group fits);
154
+ - `rule`: the rule from `defineRule` of `@oxlint/plugins`, with a
155
+ `messages` entry in `meta` for each finding. `moduleSpecifiers`
156
+ (`src/rule.ts`) builds a visitor that checks every module specifier.
157
+
158
+ A rule reads the workspace through `src/workspace/`: `packageOf` and
159
+ `installedPackage` find package manifests, `declaresEntrypoint` checks
160
+ `exports`, and `layoutFile` finds the subpath of a file.
161
+
162
+ 2. Add one line to the registry, `src/rules/index.ts`. The plugin, the
163
+ `recommended` config and the `RuleName` type follow from it.
164
+ 3. Write `src/rules/<rule>/<rule>.test.ts` against the fixture workspace in
165
+ `test/fixtures/workspace/`, and add fixture files for the new cases. Add the
166
+ new diagnostics to the expectations of `src/index.test.ts`.
167
+ 4. Add the rule to the table above, and to `CHANGELOG.md`.
168
+
169
+ Oxlint calls rule visitors synchronously, so a rule reads files with the
170
+ synchronous `node:fs` API: it cannot wait for a promise or a timer. The
171
+ workspace lookups cache each folder they visit, so each package.json is read
172
+ once per plugin load.
@@ -0,0 +1,10 @@
1
+ import type { Plugin } from "@oxlint/plugins";
2
+ import { rules } from "./rules/index.ts";
3
+ declare const plugin: Plugin;
4
+ export type RuleName = `ci-solution/${keyof typeof rules}`;
5
+ export interface RecommendedConfig {
6
+ jsPlugins: string[];
7
+ rules: Record<RuleName, "error">;
8
+ }
9
+ export declare const recommended: RecommendedConfig;
10
+ export default plugin;
package/dist/index.js ADDED
@@ -0,0 +1,354 @@
1
+ // src/index.ts
2
+ import { definePlugin } from "@oxlint/plugins";
3
+
4
+ // src/rules/package-entrypoint/package-entrypoint.ts
5
+ import { defineRule } from "@oxlint/plugins";
6
+ import path2 from "node:path";
7
+
8
+ // src/rule.ts
9
+ var isStringLiteral = (node) => node?.type === "Literal" && String(node.value) === node.value;
10
+ var moduleSpecifiers = (check) => {
11
+ const visit = (node) => {
12
+ if (isStringLiteral(node)) {
13
+ check(node);
14
+ }
15
+ };
16
+ return {
17
+ CallExpression: (node) => {
18
+ if (node.callee.type === "Identifier" && node.callee.name === "require" && node.arguments.length === 1) {
19
+ visit(node.arguments[0] ?? null);
20
+ }
21
+ },
22
+ ExportAllDeclaration: (node) => {
23
+ visit(node.source);
24
+ },
25
+ ExportNamedDeclaration: (node) => {
26
+ visit(node.source);
27
+ },
28
+ ImportDeclaration: (node) => {
29
+ visit(node.source);
30
+ },
31
+ ImportExpression: (node) => {
32
+ visit(node.source);
33
+ }
34
+ };
35
+ };
36
+
37
+ // src/workspace/package-exports.ts
38
+ var parseExports = (exports) => {
39
+ const subpaths = exports instanceof Object && !Array.isArray(exports) ? Object.entries(exports) : [];
40
+ return subpaths.some(([key]) => key.startsWith(".")) ? new Map(subpaths.map(([key, target]) => [key, target !== null])) : new Map([[".", exports !== null]]);
41
+ };
42
+ var packageInfo = (root, fields) => ({
43
+ exports: fields.exports === undefined ? null : parseExports(fields.exports),
44
+ name: fields.name ?? null,
45
+ root
46
+ });
47
+ var parsePackageSpecifier = (specifier) => {
48
+ if (/^(?:[./#]|[a-z][a-z\d+.-]*:)/iu.test(specifier)) {
49
+ return null;
50
+ }
51
+ const parts = specifier.split("/");
52
+ const nameLength = specifier.startsWith("@") ? 2 : 1;
53
+ return {
54
+ name: parts.slice(0, nameLength).join("/"),
55
+ subpath: parts.slice(nameLength).join("/")
56
+ };
57
+ };
58
+ var matchPattern = (exports, key) => {
59
+ let best = null;
60
+ for (const [pattern, exported] of exports) {
61
+ const star = pattern.indexOf("*");
62
+ const prefix = pattern.slice(0, star);
63
+ const suffix = pattern.slice(star + 1);
64
+ if (star !== -1 && key.length >= pattern.length && key.startsWith(prefix) && key.endsWith(suffix) && (best === null || prefix.length > best.prefix)) {
65
+ best = { exported, prefix: prefix.length };
66
+ }
67
+ }
68
+ return best?.exported === true;
69
+ };
70
+ var declaresEntrypoint = (target, subpath) => {
71
+ if (target.exports === null) {
72
+ return subpath !== "src" && !subpath.startsWith("src/");
73
+ }
74
+ const key = subpath === "" ? "." : `./${subpath}`;
75
+ return target.exports.get(key) ?? matchPattern(target.exports, key);
76
+ };
77
+
78
+ // src/workspace/workspace.ts
79
+ import { existsSync, readFileSync } from "node:fs";
80
+ import path from "node:path";
81
+ var readPackage = (root) => {
82
+ const file = path.join(root, "package.json");
83
+ if (!existsSync(file)) {
84
+ return null;
85
+ }
86
+ const fields = JSON.parse(readFileSync(file, "utf-8"));
87
+ return packageInfo(root, fields);
88
+ };
89
+ var owners = new Map;
90
+ var installed = new Map;
91
+ var packageOf = (target) => {
92
+ const cached = owners.get(target);
93
+ if (cached !== undefined) {
94
+ return cached;
95
+ }
96
+ const parent = path.dirname(target);
97
+ const owner = readPackage(target) ?? (parent === target ? null : packageOf(parent));
98
+ owners.set(target, owner);
99
+ return owner;
100
+ };
101
+ var installedPackage = (directory, name) => {
102
+ const key = `${directory}\x00${name}`;
103
+ const cached = installed.get(key);
104
+ if (cached !== undefined) {
105
+ return cached;
106
+ }
107
+ const parent = path.dirname(directory);
108
+ const found = readPackage(path.join(directory, "node_modules", name)) ?? (parent === directory ? null : installedPackage(parent, name));
109
+ installed.set(key, found);
110
+ return found;
111
+ };
112
+
113
+ // src/rules/package-entrypoint/package-entrypoint.ts
114
+ var packageName = (target) => target.name ?? path2.basename(target.root);
115
+ var packageEntrypoint = {
116
+ group: "package-boundaries",
117
+ rule: defineRule({
118
+ create(context) {
119
+ const directory = path2.dirname(context.filename);
120
+ const owner = packageOf(directory);
121
+ if (owner === null) {
122
+ return {};
123
+ }
124
+ return moduleSpecifiers((node) => {
125
+ const specifier = node.value;
126
+ if (specifier.startsWith(".")) {
127
+ const target = packageOf(path2.resolve(directory, specifier));
128
+ if (target !== null && target.root !== owner.root) {
129
+ context.report({
130
+ data: { name: packageName(target), specifier },
131
+ messageId: "relative",
132
+ node
133
+ });
134
+ }
135
+ return;
136
+ }
137
+ const parsed = parsePackageSpecifier(specifier);
138
+ if (parsed === null) {
139
+ return;
140
+ }
141
+ const target = parsed.name === owner.name ? owner : installedPackage(directory, parsed.name);
142
+ if (target !== null && !declaresEntrypoint(target, parsed.subpath)) {
143
+ context.report({
144
+ data: { name: parsed.name, specifier },
145
+ messageId: "undeclared",
146
+ node
147
+ });
148
+ }
149
+ });
150
+ },
151
+ meta: {
152
+ docs: {
153
+ description: "Require imports of another package to target an entrypoint declared in its package.json exports."
154
+ },
155
+ messages: {
156
+ relative: "`{{specifier}}` reaches into package `{{name}}` through a relative path. Import it by its package name and a declared entrypoint.",
157
+ undeclared: "`{{specifier}}` is not a declared entrypoint of `{{name}}`. Import the package root or a subpath listed in its package.json exports."
158
+ },
159
+ type: "problem"
160
+ }
161
+ })
162
+ };
163
+
164
+ // src/rules/subpath-entrypoint/subpath-entrypoint.ts
165
+ import { defineRule as defineRule2 } from "@oxlint/plugins";
166
+ import path4 from "node:path";
167
+
168
+ // src/workspace/layout.ts
169
+ import path3 from "node:path";
170
+ var SUBPATHS = [
171
+ "ci",
172
+ "domain",
173
+ "api",
174
+ "server",
175
+ "headless",
176
+ "views"
177
+ ];
178
+ var isSubpath = (folder) => SUBPATHS.some((subpath) => subpath === folder);
179
+ var subpathOfSegments = (segments) => {
180
+ const [folder] = segments ?? [];
181
+ return isSubpath(folder) ? folder : null;
182
+ };
183
+ var sourceSegments = (root, target) => {
184
+ const relative = path3.relative(path3.join(root, "src"), target);
185
+ return relative === "" || relative.startsWith("..") || path3.isAbsolute(relative) ? null : relative.split(path3.sep);
186
+ };
187
+ var isSubpathEntrypoint = (segments) => {
188
+ const [, file] = segments;
189
+ return segments.length === 1 || segments.length === 2 && path3.parse(file ?? "").name === "index";
190
+ };
191
+ var layoutFile = (filename) => {
192
+ const owner = packageOf(path3.dirname(filename));
193
+ const segments = owner && sourceSegments(owner.root, filename);
194
+ const subpath = segments && segments.length > 1 ? subpathOfSegments(segments) : null;
195
+ return owner && subpath ? { owner, subpath } : null;
196
+ };
197
+
198
+ // src/rules/subpath-entrypoint/subpath-entrypoint.ts
199
+ var subpathEntrypoint = {
200
+ group: "solution-layout",
201
+ rule: defineRule2({
202
+ create(context) {
203
+ const directory = path4.dirname(context.filename);
204
+ const owner = packageOf(directory);
205
+ const source = owner === null ? null : sourceSegments(owner.root, context.filename);
206
+ if (owner === null || source === null) {
207
+ return {};
208
+ }
209
+ const sourceSubpath = source.length > 1 ? subpathOfSegments(source) : null;
210
+ return moduleSpecifiers((node) => {
211
+ if (!node.value.startsWith(".")) {
212
+ return;
213
+ }
214
+ const target = sourceSegments(owner.root, path4.resolve(directory, node.value));
215
+ const targetSubpath = subpathOfSegments(target);
216
+ if (target !== null && targetSubpath !== null && targetSubpath !== sourceSubpath && !isSubpathEntrypoint(target)) {
217
+ context.report({
218
+ data: { specifier: node.value, target: targetSubpath },
219
+ messageId: "deep",
220
+ node
221
+ });
222
+ }
223
+ });
224
+ },
225
+ meta: {
226
+ docs: {
227
+ description: "Require imports from another subpath to target the subpath folder or its index file."
228
+ },
229
+ messages: {
230
+ deep: "`{{specifier}}` reaches past the `{{target}}/` entrypoint. Import the subpath folder, which resolves to its index.ts or index.tsx."
231
+ },
232
+ type: "problem"
233
+ }
234
+ })
235
+ };
236
+
237
+ // src/rules/subpath-imports/subpath-imports.ts
238
+ import { defineRule as defineRule3 } from "@oxlint/plugins";
239
+ import path5 from "node:path";
240
+ var FORBIDDEN_IMPORTS = {
241
+ api: [],
242
+ ci: ["views", "headless"],
243
+ domain: ["server", "views", "headless", "ci"],
244
+ headless: ["server"],
245
+ server: [],
246
+ views: ["server"]
247
+ };
248
+ var subpathImports = {
249
+ group: "solution-layout",
250
+ rule: defineRule3({
251
+ create(context) {
252
+ const file = layoutFile(context.filename);
253
+ if (file === null) {
254
+ return {};
255
+ }
256
+ const { owner, subpath } = file;
257
+ const directory = path5.dirname(context.filename);
258
+ const forbidden = FORBIDDEN_IMPORTS[subpath];
259
+ const targetSubpath = (specifier) => {
260
+ if (specifier.startsWith(".")) {
261
+ return subpathOfSegments(sourceSegments(owner.root, path5.resolve(directory, specifier)));
262
+ }
263
+ const parsed = parsePackageSpecifier(specifier);
264
+ return parsed !== null && parsed.name === owner.name ? subpathOfSegments(parsed.subpath.split("/")) : null;
265
+ };
266
+ return moduleSpecifiers((node) => {
267
+ const target = targetSubpath(node.value);
268
+ if (target !== null && forbidden.includes(target)) {
269
+ context.report({
270
+ data: {
271
+ forbidden: forbidden.map((name) => `\`${name}/\``).join(", "),
272
+ source: subpath,
273
+ specifier: node.value,
274
+ target
275
+ },
276
+ messageId: "forbidden",
277
+ node
278
+ });
279
+ }
280
+ });
281
+ },
282
+ meta: {
283
+ docs: {
284
+ description: "Disallow imports between subpaths that break the solution package layout direction."
285
+ },
286
+ messages: {
287
+ forbidden: "`{{specifier}}` imports `{{target}}/` from `{{source}}/`. `{{source}}/` must not import {{forbidden}}."
288
+ },
289
+ type: "problem"
290
+ }
291
+ })
292
+ };
293
+
294
+ // src/rules/subpath-index/subpath-index.ts
295
+ import { defineRule as defineRule4 } from "@oxlint/plugins";
296
+ import { existsSync as existsSync2 } from "node:fs";
297
+ import path6 from "node:path";
298
+ var INDEX_FILES = ["index.ts", "index.tsx"];
299
+ var subpathIndex = {
300
+ group: "solution-layout",
301
+ rule: defineRule4({
302
+ create(context) {
303
+ const file = layoutFile(context.filename);
304
+ if (file === null) {
305
+ return {};
306
+ }
307
+ const folder = path6.join(file.owner.root, "src", file.subpath);
308
+ if (INDEX_FILES.some((name) => existsSync2(path6.join(folder, name)))) {
309
+ return {};
310
+ }
311
+ return {
312
+ Program: (node) => {
313
+ context.report({
314
+ data: { subpath: file.subpath },
315
+ messageId: "missing",
316
+ node
317
+ });
318
+ }
319
+ };
320
+ },
321
+ meta: {
322
+ docs: {
323
+ description: "Require every subpath folder to have an index.ts or index.tsx entrypoint."
324
+ },
325
+ messages: {
326
+ missing: "Subpath folder `src/{{subpath}}/` has no index.ts or index.tsx entrypoint."
327
+ },
328
+ type: "problem"
329
+ }
330
+ })
331
+ };
332
+
333
+ // src/rules/index.ts
334
+ var rules = {
335
+ "package-entrypoint": packageEntrypoint,
336
+ "subpath-entrypoint": subpathEntrypoint,
337
+ "subpath-imports": subpathImports,
338
+ "subpath-index": subpathIndex
339
+ };
340
+
341
+ // src/index.ts
342
+ var plugin = definePlugin({
343
+ meta: { name: "ci-solution" },
344
+ rules: Object.fromEntries(Object.entries(rules).map(([name, { rule }]) => [name, rule]))
345
+ });
346
+ var recommended = {
347
+ jsPlugins: ["@collective-intelligence/lint"],
348
+ rules: Object.fromEntries(Object.keys(rules).map((name) => [`ci-solution/${name}`, "error"]))
349
+ };
350
+ var src_default = plugin;
351
+ export {
352
+ src_default as default,
353
+ recommended
354
+ };
package/dist/rule.d.ts ADDED
@@ -0,0 +1,7 @@
1
+ import type { ESTree, Rule, Visitor } from "@oxlint/plugins";
2
+ export type RuleGroup = "package-boundaries" | "solution-layout";
3
+ export interface LintRule {
4
+ readonly group: RuleGroup;
5
+ readonly rule: Rule;
6
+ }
7
+ export declare const moduleSpecifiers: (check: (literal: ESTree.StringLiteral) => void) => Visitor;
@@ -0,0 +1,7 @@
1
+ import type { LintRule } from "../rule.ts";
2
+ export declare const rules: {
3
+ readonly "package-entrypoint": LintRule;
4
+ readonly "subpath-entrypoint": LintRule;
5
+ readonly "subpath-imports": LintRule;
6
+ readonly "subpath-index": LintRule;
7
+ };
@@ -0,0 +1,2 @@
1
+ import type { LintRule } from "../../rule.ts";
2
+ export declare const packageEntrypoint: LintRule;
@@ -0,0 +1,2 @@
1
+ import type { LintRule } from "../../rule.ts";
2
+ export declare const subpathEntrypoint: LintRule;
@@ -0,0 +1,2 @@
1
+ import type { LintRule } from "../../rule.ts";
2
+ export declare const subpathImports: LintRule;
@@ -0,0 +1,2 @@
1
+ import type { LintRule } from "../../rule.ts";
2
+ export declare const subpathIndex: LintRule;
@@ -0,0 +1,11 @@
1
+ import type { PackageInfo } from "./package-exports.ts";
2
+ export declare const SUBPATHS: readonly ["ci", "domain", "api", "server", "headless", "views"];
3
+ export type Subpath = (typeof SUBPATHS)[number];
4
+ export declare const subpathOfSegments: (segments: readonly string[] | null) => Subpath | null;
5
+ export declare const sourceSegments: (root: string, target: string) => readonly string[] | null;
6
+ export declare const isSubpathEntrypoint: (segments: readonly string[]) => boolean;
7
+ export interface LayoutFile {
8
+ readonly owner: PackageInfo;
9
+ readonly subpath: Subpath;
10
+ }
11
+ export declare const layoutFile: (filename: string) => LayoutFile | null;
@@ -0,0 +1,19 @@
1
+ export type ExportsField = string | null | readonly ExportsField[] | {
2
+ readonly [key: string]: ExportsField;
3
+ };
4
+ export interface Manifest {
5
+ readonly exports?: ExportsField;
6
+ readonly name?: string;
7
+ }
8
+ export interface PackageInfo {
9
+ readonly exports: ReadonlyMap<string, boolean> | null;
10
+ readonly name: string | null;
11
+ readonly root: string;
12
+ }
13
+ export declare const packageInfo: (root: string, fields: Manifest) => PackageInfo;
14
+ export interface PackageSpecifier {
15
+ readonly name: string;
16
+ readonly subpath: string;
17
+ }
18
+ export declare const parsePackageSpecifier: (specifier: string) => PackageSpecifier | null;
19
+ export declare const declaresEntrypoint: (target: PackageInfo, subpath: string) => boolean;
@@ -0,0 +1,3 @@
1
+ import type { PackageInfo } from "./package-exports.ts";
2
+ export declare const packageOf: (target: string) => PackageInfo | null;
3
+ export declare const installedPackage: (directory: string, name: string) => PackageInfo | null;
package/package.json CHANGED
@@ -1,6 +1,50 @@
1
1
  {
2
2
  "name": "@collective-intelligence/lint",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.19",
4
+ "description": "Oxlint plugin with the Collective Intelligence lint rules: package boundaries and solution package layout.",
5
+ "type": "module",
6
+ "sideEffects": false,
7
+ "files": [
8
+ "dist",
9
+ "README.md"
10
+ ],
11
+ "main": "./dist/index.js",
12
+ "types": "./dist/index.d.ts",
13
+ "exports": {
14
+ ".": {
15
+ "ci-source": "./src/index.ts",
16
+ "types": "./dist/index.d.ts",
17
+ "import": "./dist/index.js",
18
+ "default": "./dist/index.js"
19
+ }
20
+ },
21
+ "scripts": {
22
+ "build": "bun tools/build.ts",
23
+ "test": "bun test src && bun run test:pack",
24
+ "test:pack": "bun run build && bun tools/test-pack.ts",
25
+ "test:unit": "bun test src",
26
+ "format": "oxfmt --write .",
27
+ "format:check": "oxfmt --check .",
28
+ "format:check:staged": "oxfmt --check",
29
+ "lint:staged": "oxlint --no-error-on-unmatched-pattern",
30
+ "prepack": "bun run build",
31
+ "type-check": "bun check",
32
+ "lint": "oxlint src test tools"
33
+ },
34
+ "publishConfig": {
35
+ "access": "public"
36
+ },
37
+ "dependencies": {
38
+ "@oxlint/plugins": "^1.85.0"
39
+ },
40
+ "peerDependencies": {
41
+ "oxlint": ">=1.82.0"
42
+ },
43
+ "devDependencies": {
44
+ "bun-types": "^1.4.2",
45
+ "oxfmt": "0.66.0",
46
+ "oxlint": "1.82.0",
47
+ "typescript": "7.0.2",
48
+ "ultracite": "7.10.7"
49
+ }
50
+ }