diff --git a/README.md b/README.md index 5bfe9be..4544e81 100644 --- a/README.md +++ b/README.md @@ -1,53 +1,21 @@ # effect-ts-skills -Reusable Effect-TS skills and compliance tooling for [Codex](https://github.com/openai/codex). +Codex plugin with an Effect-TS guide skill and a bundled `effect-ts-check` CLI. -## Plugin - -This repository publishes one Codex plugin: - -| Plugin | Path | Bundled skill | -|--------|------|---------------| -| `effect-ts-skills` | `plugins/effect-ts-skills` | `effect-ts-guide` | - -The `effect-ts-guide` skill is intentionally kept inside the plugin. There is no separate root-level standalone skill copy. - -## Installation +## Install ```bash codex plugin marketplace add ProverCoderAI/effect-ts-skills codex plugin add effect-ts-skills@effect-ts-skills ``` -For local development from a checkout: - -```bash -codex plugin marketplace add . -codex plugin add effect-ts-skills@effect-ts-skills -``` - -The repo marketplace entry points at `plugins/effect-ts-skills`, which is the canonical plugin directory. - -After installation, start a new Codex thread and invoke the skill explicitly with `$effect-ts-guide` or ask for an Effect-TS implementation/review task. +## Use -## Development +Start a new Codex thread and ask for `$effect-ts-guide`, or ask Codex to review/fix Effect-TS code. -This repository is a [pnpm workspace](https://pnpm.io/workspaces). +## Develop ```bash corepack pnpm install -corepack pnpm run sync:distribution corepack pnpm run check ``` - -### Structure - -``` -plugins/effect-ts-skills/ # Installable Codex plugin with bundled effect-ts-guide skill -packages/effect-ts-check/ # Reusable Effect-TS compliance CLI -tools/ # Repo-level validation scripts -``` - -## License - -ISC diff --git a/packages/effect-ts-check/package.json b/packages/effect-ts-check/package.json index 1120e52..0bffdb1 100644 --- a/packages/effect-ts-check/package.json +++ b/packages/effect-ts-check/package.json @@ -18,6 +18,9 @@ "./strict": { "import": "./src/strict.mjs" }, + "./strict-format": { + "import": "./src/strict-format.mjs" + }, "./rules": { "import": "./src/rules/index.mjs" }, @@ -42,6 +45,7 @@ }, "dependencies": { "@effect/eslint-plugin": "^0.3.2", + "@eslint-community/eslint-plugin-eslint-comments": "^4.7.2", "@eslint/js": "^10.0.0", "eslint": "^10.0.0", "typescript-eslint": "^8.57.1" diff --git a/packages/effect-ts-check/src/base.mjs b/packages/effect-ts-check/src/base.mjs index f48c064..e4c0866 100644 --- a/packages/effect-ts-check/src/base.mjs +++ b/packages/effect-ts-check/src/base.mjs @@ -3,6 +3,12 @@ import tseslint from "typescript-eslint"; import { effectSyntaxRestrictions } from "./rules/index.mjs"; export const effectFileGlobs = Object.freeze(["**/*.{js,jsx,mjs,cjs,ts,tsx,mts,cts}"]); +export const effectCoreFileGlobs = Object.freeze([ + "**/src/core/**/*.{js,jsx,mjs,cjs,ts,tsx,mts,cts}", +]); +export const effectCoreAxiomsFileGlobs = Object.freeze([ + "**/src/core/axioms.{ts,tsx,mts,cts}", +]); export const effectIgnoreGlobs = Object.freeze([ "tests/**", "**/tests/**", diff --git a/packages/effect-ts-check/src/index.mjs b/packages/effect-ts-check/src/index.mjs index 78d4c08..bf84c85 100644 --- a/packages/effect-ts-check/src/index.mjs +++ b/packages/effect-ts-check/src/index.mjs @@ -1,10 +1,15 @@ export { minimal } from "./minimal.mjs"; export { + effectCoreRestrictedImportPatterns, effectRestrictedImportPatterns, effectRestrictedImports, + effectCoreSyntaxRestrictions, + effectErrorBoundarySyntaxRestrictions, + effectHostSyntaxRestrictions, effectStrictSyntaxRestrictions, effectSyntaxRestrictions, effectTypeRules, } from "./rules/index.mjs"; export { getProfileConfig, lintPaths, main, parseArguments } from "./run.mjs"; export { strict } from "./strict.mjs"; +export { strictFormat } from "./strict-format.mjs"; diff --git a/packages/effect-ts-check/src/rules/imports.mjs b/packages/effect-ts-check/src/rules/imports.mjs index 476a1a6..d3dec3a 100644 --- a/packages/effect-ts-check/src/rules/imports.mjs +++ b/packages/effect-ts-check/src/rules/imports.mjs @@ -55,3 +55,17 @@ export const effectRestrictedImportPatterns = Object.freeze([ message: "Do not import from node:* directly. Use @effect/platform services.", }, ]); + +export const effectCoreRestrictedImportPatterns = Object.freeze([ + { + group: [ + "../shell/**", + "../../shell/**", + "../../../shell/**", + "./shell/**", + "src/shell/**", + "shell/**", + ], + message: "CORE must not import from SHELL.", + }, +]); diff --git a/packages/effect-ts-check/src/rules/index.mjs b/packages/effect-ts-check/src/rules/index.mjs index 5948465..0be1141 100644 --- a/packages/effect-ts-check/src/rules/index.mjs +++ b/packages/effect-ts-check/src/rules/index.mjs @@ -1,3 +1,13 @@ -export { effectRestrictedImportPatterns, effectRestrictedImports } from "./imports.mjs"; -export { effectStrictSyntaxRestrictions, effectSyntaxRestrictions } from "./syntax.mjs"; +export { + effectCoreRestrictedImportPatterns, + effectRestrictedImportPatterns, + effectRestrictedImports, +} from "./imports.mjs"; +export { + effectCoreSyntaxRestrictions, + effectErrorBoundarySyntaxRestrictions, + effectHostSyntaxRestrictions, + effectStrictSyntaxRestrictions, + effectSyntaxRestrictions, +} from "./syntax.mjs"; export { effectTypeRules } from "./types.mjs"; diff --git a/packages/effect-ts-check/src/rules/syntax.mjs b/packages/effect-ts-check/src/rules/syntax.mjs index eba8f22..fc58443 100644 --- a/packages/effect-ts-check/src/rules/syntax.mjs +++ b/packages/effect-ts-check/src/rules/syntax.mjs @@ -29,7 +29,7 @@ export const effectSyntaxRestrictions = Object.freeze([ }, ]); -export const effectStrictSyntaxRestrictions = Object.freeze([ +export const effectHostSyntaxRestrictions = Object.freeze([ { selector: "CallExpression[callee.name='fetch']", message: "Use @effect/platform HttpClient instead of fetch.", @@ -50,6 +50,9 @@ export const effectStrictSyntaxRestrictions = Object.freeze([ selector: "CallExpression[callee.object.name='global'][callee.property.name='fetch']", message: "Use @effect/platform HttpClient instead of global.fetch.", }, +]); + +export const effectErrorBoundarySyntaxRestrictions = Object.freeze([ { selector: "CallExpression[callee.property.name='catchAll']", message: "Avoid catchAll that swallows typed errors; map or rethrow explicitly.", @@ -62,8 +65,27 @@ export const effectStrictSyntaxRestrictions = Object.freeze([ selector: "TSTypeAssertion", message: "Avoid casts in product code; keep them in one axioms boundary if needed.", }, +]); + +export const effectCoreSyntaxRestrictions = Object.freeze([ { selector: "TSUnknownKeyword", message: "Use unknown only at shell boundaries with decoding.", }, + { + selector: "CallExpression[callee.property.name='runSyncExit']", + message: "Use Effect.runSyncExit only at shell/runtime boundaries.", + }, + { + selector: "CallExpression[callee.property.name='runSync']", + message: "Use Effect.runSync only at shell/runtime boundaries.", + }, + { + selector: "CallExpression[callee.property.name='runPromise']", + message: "Use Effect.runPromise only at shell/runtime boundaries.", + }, +]); + +export const effectStrictSyntaxRestrictions = Object.freeze([ + ...effectHostSyntaxRestrictions, ]); diff --git a/packages/effect-ts-check/src/run.mjs b/packages/effect-ts-check/src/run.mjs index bf2d876..2d887d2 100644 --- a/packages/effect-ts-check/src/run.mjs +++ b/packages/effect-ts-check/src/run.mjs @@ -2,6 +2,7 @@ import { ESLint } from "eslint"; import { minimal } from "./minimal.mjs"; import { strict } from "./strict.mjs"; +import { strictFormat } from "./strict-format.mjs"; export function parseArguments(argv) { const result = { @@ -40,6 +41,10 @@ export function parseArguments(argv) { } export function getProfileConfig(profile) { + if (profile === "strict-format") { + return strictFormat; + } + if (profile === "strict") { return strict; } @@ -52,6 +57,13 @@ export function getProfileConfig(profile) { } function resolveProfileConfig(profile) { + if (profile === "strict-format") { + return { + ok: true, + config: strictFormat, + }; + } + if (profile === "strict") { return { ok: true, @@ -78,10 +90,13 @@ export function printUsage() { "Usage:", " effect-ts-check [paths...]", " effect-ts-check --profile strict [paths...]", + " effect-ts-check --profile strict-format [paths...]", "", "Profiles:", " minimal Default fast effect compliance check.", - " strict Adds import/type/host API policy checks.", + " strict Adds import/type/host API and Effect boundary policy checks.", + " strict-format", + " Runs strict plus the official @effect/dprint formatting preset.", "", ].join("\n"), ); diff --git a/packages/effect-ts-check/src/strict-format.mjs b/packages/effect-ts-check/src/strict-format.mjs new file mode 100644 index 0000000..352da25 --- /dev/null +++ b/packages/effect-ts-check/src/strict-format.mjs @@ -0,0 +1,10 @@ +import * as effectEslint from "@effect/eslint-plugin"; + +import { strict } from "./strict.mjs"; + +export const strictFormat = [ + ...strict, + ...effectEslint.configs.dprint, +]; + +export default strictFormat; diff --git a/packages/effect-ts-check/src/strict.mjs b/packages/effect-ts-check/src/strict.mjs index db79c24..23415e2 100644 --- a/packages/effect-ts-check/src/strict.mjs +++ b/packages/effect-ts-check/src/strict.mjs @@ -1,8 +1,16 @@ -import * as effectEslint from "@effect/eslint-plugin"; +import eslintComments from "@eslint-community/eslint-plugin-eslint-comments"; import tseslint from "typescript-eslint"; -import { effectBaseConfig, effectFileGlobs } from "./base.mjs"; import { + effectCoreAxiomsFileGlobs, + effectCoreFileGlobs, + effectFileGlobs, + effectIgnoreConfig, +} from "./base.mjs"; +import { + effectCoreRestrictedImportPatterns, + effectCoreSyntaxRestrictions, + effectErrorBoundarySyntaxRestrictions, effectRestrictedImportPatterns, effectRestrictedImports, effectStrictSyntaxRestrictions, @@ -10,9 +18,33 @@ import { effectTypeRules, } from "./rules/index.mjs"; +const strictSyntaxRestrictions = Object.freeze([ + ...effectSyntaxRestrictions, + ...effectStrictSyntaxRestrictions, +]); + +const coreSyntaxRestrictions = Object.freeze([ + ...strictSyntaxRestrictions, + ...effectErrorBoundarySyntaxRestrictions, + ...effectCoreSyntaxRestrictions, +]); + +const coreAxiomsSyntaxRestrictions = Object.freeze([ + ...strictSyntaxRestrictions, + ...effectErrorBoundarySyntaxRestrictions.filter((rule) => + rule.selector !== "TSAsExpression" && rule.selector !== "TSTypeAssertion" + ), + ...effectCoreSyntaxRestrictions, +]); + +const strictImportPatterns = Object.freeze([...effectRestrictedImportPatterns]); +const coreImportPatterns = Object.freeze([ + ...effectRestrictedImportPatterns, + ...effectCoreRestrictedImportPatterns, +]); + export const strict = [ - ...effectBaseConfig, - ...effectEslint.configs.dprint, + effectIgnoreConfig, { name: "effect-ts-check/strict", files: effectFileGlobs, @@ -23,22 +55,45 @@ export const strict = [ }, plugins: { "@typescript-eslint": tseslint.plugin, + "eslint-comments": eslintComments, }, rules: { "no-console": "error", + "no-throw-literal": "error", "no-restricted-imports": [ "error", { paths: effectRestrictedImports, - patterns: effectRestrictedImportPatterns, + patterns: strictImportPatterns, }, ], - "no-restricted-syntax": [ + "no-restricted-syntax": ["error", ...strictSyntaxRestrictions], + "eslint-comments/no-use": "error", + "eslint-comments/no-unlimited-disable": "error", + "eslint-comments/disable-enable-pair": "error", + "eslint-comments/no-unused-disable": "error", + ...effectTypeRules, + }, + }, + { + name: "effect-ts-check/strict-core", + files: effectCoreFileGlobs, + rules: { + "no-restricted-imports": [ "error", - ...effectSyntaxRestrictions, - ...effectStrictSyntaxRestrictions, + { + paths: effectRestrictedImports, + patterns: coreImportPatterns, + }, ], - ...effectTypeRules, + "no-restricted-syntax": ["error", ...coreSyntaxRestrictions], + }, + }, + { + name: "effect-ts-check/strict-core-axioms", + files: effectCoreAxiomsFileGlobs, + rules: { + "no-restricted-syntax": ["error", ...coreAxiomsSyntaxRestrictions], }, }, ]; diff --git a/packages/effect-ts-check/tests/cli.test.mjs b/packages/effect-ts-check/tests/cli.test.mjs index de90233..5cffd81 100644 --- a/packages/effect-ts-check/tests/cli.test.mjs +++ b/packages/effect-ts-check/tests/cli.test.mjs @@ -80,6 +80,23 @@ test("cli supports strict profile", () => { assert.match(result.stdout, /no-restricted-imports|@typescript-eslint\/no-explicit-any|no-console/) }) +test("cli supports strict-format profile", () => { + const fixture = writeTempFixture("pass.ts", "const value = 1;\nexport { value };\n") + const result = spawnSync( + process.execPath, + [cliPath, "--profile", "strict-format", fixture.path], + { + cwd: packageDir, + encoding: "utf8" + } + ) + fixture.cleanup() + + assert.equal(result.status, 0) + assert.equal(result.stdout, "") + assert.equal(result.stderr, "") +}) + test("cli strict profile keeps minimal syntax checks", () => { const fixture = writeTempFixture( "fail.ts", diff --git a/packages/effect-ts-check/tests/configs.test.mjs b/packages/effect-ts-check/tests/configs.test.mjs index 7110540..6ce8457 100644 --- a/packages/effect-ts-check/tests/configs.test.mjs +++ b/packages/effect-ts-check/tests/configs.test.mjs @@ -1,7 +1,7 @@ import assert from "node:assert/strict" import test from "node:test" -import { minimal, strict } from "../src/index.mjs" +import { minimal, strict, strictFormat } from "../src/index.mjs" import { lintSnippet } from "./helpers.mjs" test("minimal exports a flat config array", () => { @@ -49,10 +49,24 @@ test("strict retains minimal syntax policy", async () => { assert.ok(result.messages.some((message) => message.ruleId === "no-restricted-syntax")) }) -test("strict includes additional effect-eslint preset layers", () => { +test("strict adds additional compliance layers", () => { assert.ok(strict.length > minimal.length) }) +test("strict-format adds the effect dprint preset separately", () => { + assert.ok(strictFormat.length > strict.length) + assert.ok( + strictFormat.some((entry) => + entry.rules && Object.hasOwn(entry.rules, "@effect/dprint") + ) + ) + assert.ok( + strict.every((entry) => + !entry.rules || !Object.hasOwn(entry.rules, "@effect/dprint") + ) + ) +}) + test("minimal leaves import and type policy to strict", async () => { const [result] = await lintSnippet( minimal, @@ -95,6 +109,108 @@ test("strict applies to common module extensions", async () => { } }) +test("strict rejects eslint disable comments", async () => { + const [result] = await lintSnippet( + strict, + "/* eslint-disable no-console */\nconsole.log(1)\n", + "demo.ts" + ) + + const ruleIds = result.messages.map((message) => message.ruleId) + + assert.ok(ruleIds.includes("eslint-comments/no-use")) +}) + +test("strict rejects direct fetch", async () => { + const [result] = await lintSnippet( + strict, + "fetch('https://example.com')\n", + "demo.ts" + ) + + assert.ok(result.messages.some((message) => message.ruleId === "no-restricted-syntax")) +}) + +test("strict rejects thrown literals without typed linting", async () => { + const [result] = await lintSnippet( + strict, + "throw 'BadError: failed'\n", + "demo.ts" + ) + + assert.ok(result.messages.some((message) => message.ruleId === "no-throw-literal")) +}) + +test("strict keeps runtime execution at shell boundaries", async () => { + const coreResult = await lintSnippet( + strict, + "Effect.runPromise(Effect.succeed(1))\n", + "src/core/program.ts" + ) + const shellResult = await lintSnippet( + strict, + "Effect.runPromise(Effect.succeed(1))\n", + "src/shell/program.ts" + ) + + assert.ok( + coreResult[0].messages.some((message) => message.ruleId === "no-restricted-syntax") + ) + assert.ok( + shellResult[0].messages.every((message) => message.ruleId !== "no-restricted-syntax") + ) +}) + +test("strict blocks core imports from shell", async () => { + const [result] = await lintSnippet( + strict, + 'import { service } from "../shell/service"\nexport { service }\n', + "src/core/usecase.ts" + ) + + assert.ok(result.messages.some((message) => message.ruleId === "no-restricted-imports")) +}) + +test("strict keeps casts inside the axioms boundary", async () => { + const coreResult = await lintSnippet( + strict, + "const value = input as string\n", + "src/core/usecase.ts" + ) + const axiomsResult = await lintSnippet( + strict, + "const value = input as string\n", + "src/core/axioms.ts" + ) + + assert.ok( + coreResult[0].messages.some((message) => message.ruleId === "no-restricted-syntax") + ) + assert.ok( + axiomsResult[0].messages.every((message) => message.ruleId !== "no-restricted-syntax") + ) +}) + +test("strict keeps catchAll out of core while allowing outer handlers", async () => { + const coreResult = await lintSnippet( + strict, + "const handler = Effect.catchAll(() => Effect.succeed(1))\n", + "src/core/usecase.ts" + ) + const apiResult = await lintSnippet( + strict, + "const handler = Effect.catchAll(() => Effect.succeed(1))\n", + "src/api/http.ts" + ) + + assert.ok( + coreResult[0].messages.some((message) => message.ruleId === "no-restricted-syntax") + ) + assert.ok( + apiResult[0].messages.every((message) => message.ruleId !== "no-restricted-syntax") + ) +}) + test("minimal ignores nested tests and fixtures", async () => { const [testResult] = await lintSnippet( minimal, diff --git a/plugins/effect-ts-skills/skills/effect-ts-guide/SKILL.md b/plugins/effect-ts-skills/skills/effect-ts-guide/SKILL.md index 7df5087..1da986d 100644 --- a/plugins/effect-ts-skills/skills/effect-ts-guide/SKILL.md +++ b/plugins/effect-ts-skills/skills/effect-ts-guide/SKILL.md @@ -23,9 +23,15 @@ If the repository is mostly tooling, docs, or test fixtures for the checker itse bash "$SKILL_DIR/scripts/run-effect-ts-check.sh" --profile strict ``` -4. Fix the violations that are machine-detectable. -5. Apply the manual rules from the references for architecture and style decisions. -6. Re-run the relevant check before finishing. +4. If the repository wants the official Effect dprint formatting gate too, run: + +```bash +bash "$SKILL_DIR/scripts/run-effect-ts-check.sh" --profile strict-format +``` + +5. Fix the violations that are machine-detectable. +6. Apply the manual rules from the references for architecture and style decisions. +7. Re-run the relevant check before finishing. For editor integration tasks, treat `effect-ts-check` as the reusable CLI/compliance package and `@effect/language-service` plus VSCode settings/extensions as a separate setup concern. @@ -42,7 +48,9 @@ The compliance check is for fast, repeatable signals: - `require` - common JavaScript and TypeScript module extensions, including `.jsx`, `.mts`, and `.cts` -The `strict` profile also layers in unsafe host import checks, obvious typing-policy violations such as `any` and `ts-ignore`, unsupported casts, direct `fetch`, `catchAll`, and the official `@effect/eslint-plugin` preset for Effect-aware lint behavior. +The `strict` profile also layers in unsafe host import checks, obvious typing-policy violations such as `any` and `ts-ignore`, direct `fetch`, eslint-disable hygiene, thrown-literal checks, and path-aware CORE boundary rules for casts, `unknown`, `catchAll`, CORE-to-SHELL imports, and runtime execution calls such as `Effect.runPromise` / `Effect.runSync`. + +The `strict-format` profile runs `strict` plus the official `@effect/dprint` formatting preset. Use it when formatting should be part of the compliance gate; use `strict` when you need semantic policy results without formatting noise. The wrapper uses `npx` with a bundled `effect-ts-check` tarball. It can run from a standalone skill install, but npm registry access or an npm cache is required for package dependencies. @@ -56,7 +64,7 @@ The check does not replace architectural reasoning. Apply manual rules for: - boundary decoding with `@effect/schema` - resource safety with `Effect.acquireRelease` and `Effect.scoped` - exhaustive handling of unions -- whether `Effect.runPromise`, `Effect.runSync`, and similar runtime execution calls are isolated to shell entrypoints +- project-specific service factories, local package boundaries, and allowed runtime entrypoint layouts outside the default CORE/SHELL convention ## References diff --git a/plugins/effect-ts-skills/skills/effect-ts-guide/assets/effect-ts-check/prover-coder-ai-effect-ts-check-0.1.0.tgz b/plugins/effect-ts-skills/skills/effect-ts-guide/assets/effect-ts-check/prover-coder-ai-effect-ts-check-0.1.0.tgz index fd06654..b5fe878 100644 Binary files a/plugins/effect-ts-skills/skills/effect-ts-guide/assets/effect-ts-check/prover-coder-ai-effect-ts-check-0.1.0.tgz and b/plugins/effect-ts-skills/skills/effect-ts-guide/assets/effect-ts-check/prover-coder-ai-effect-ts-check-0.1.0.tgz differ diff --git a/plugins/effect-ts-skills/skills/effect-ts-guide/references/lint-checks.md b/plugins/effect-ts-skills/skills/effect-ts-guide/references/lint-checks.md index e686a36..2368cdb 100644 --- a/plugins/effect-ts-skills/skills/effect-ts-guide/references/lint-checks.md +++ b/plugins/effect-ts-skills/skills/effect-ts-guide/references/lint-checks.md @@ -30,14 +30,31 @@ The minimal profile should catch the high-signal Effect violations: The strict profile should add deeper policy checks: -- the official `@effect/eslint-plugin` preset - direct host imports that bypass Effect platform services - obvious unsafe typing policy violations - safer handling around casts and `unknown` - direct `fetch` and other host API restrictions - eslint comment hygiene +- thrown-literal checks +- path-aware CORE/SHELL boundary checks -Runtime execution boundaries such as `Effect.runPromise` and CORE/SHELL import direction still need manual review because the correct answer depends on the repository's entrypoint layout. +The CORE/SHELL checks apply to `src/core/**` by default: + +- CORE must not import from SHELL. +- `Effect.runPromise`, `Effect.runSync`, and `Effect.runSyncExit` are shell/runtime-boundary only. +- `catchAll`, casts, and `unknown` are blocked in CORE, except casts are allowed in `src/core/axioms.ts`. + +## Strict Format Profile + +Use `--profile strict-format` when formatting should be part of the gate: + +```bash +bash "$SKILL_DIR/scripts/run-effect-ts-check.sh" --profile strict-format +``` + +This runs `strict` plus the official `@effect/dprint` preset. Keep it separate from `strict` when you need actionable semantic findings without formatting noise. + +Runtime execution boundaries outside the default `src/core/**` / `src/shell/**` convention still need manual review because the correct answer depends on the repository's entrypoint layout. ## Editor Tooling Boundary diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4436f43..3b25589 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -13,6 +13,9 @@ importers: '@effect/eslint-plugin': specifier: ^0.3.2 version: 0.3.2 + '@eslint-community/eslint-plugin-eslint-comments': + specifier: ^4.7.2 + version: 4.7.2(eslint@10.2.0) '@eslint/js': specifier: ^10.0.0 version: 10.0.1(eslint@10.2.0) @@ -34,6 +37,12 @@ packages: '@effect/eslint-plugin@0.3.2': resolution: {integrity: sha512-c4Vs9t3r54A4Zpl+wo8+PGzZz3JWYsip41H+UrebRLjQ2Hk/ap63IeCgN/HWcYtxtyhRopjp7gW9nOQ2Snbl+g==} + '@eslint-community/eslint-plugin-eslint-comments@4.7.2': + resolution: {integrity: sha512-LF03qURSwEWm2dz5wtdDCzNk+7Opl0X7q6I3undsaIuNsEiNvRV3BCtqu14Q/6Pzg1tBj44LcxpW2EpSLZStZw==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0 + '@eslint-community/eslint-utils@4.9.1': resolution: {integrity: sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -436,6 +445,12 @@ snapshots: '@dprint/typescript': 0.91.8 prettier-linter-helpers: 1.0.1 + '@eslint-community/eslint-plugin-eslint-comments@4.7.2(eslint@10.2.0)': + dependencies: + escape-string-regexp: 4.0.0 + eslint: 10.2.0 + ignore: 7.0.5 + '@eslint-community/eslint-utils@4.9.1(eslint@10.2.0)': dependencies: eslint: 10.2.0 diff --git a/tools/validate-distribution.mjs b/tools/validate-distribution.mjs index 040b724..cb9d41b 100644 --- a/tools/validate-distribution.mjs +++ b/tools/validate-distribution.mjs @@ -185,18 +185,27 @@ function assertLintCheckDocs() { "`unknown`", "`fetch`", "host API restrictions", + "CORE/SHELL", ]) { if (!strictSection.includes(expected)) { fail(`Strict profile docs must mention ${expected}`) } } + if (!contents.includes("## Strict Format Profile")) { + fail("Lint docs must document strict-format profile") + } + + if (!contents.includes("@effect/dprint")) { + fail("Lint docs must mention strict-format dprint behavior") + } + if (!contents.includes("Runtime execution boundaries")) { fail("Lint docs must call runtime execution boundaries manual review") } - if (!contents.includes("CORE/SHELL import direction")) { - fail("Lint docs must call CORE/SHELL import direction manual review") + if (!contents.includes("CORE must not import from SHELL")) { + fail("Lint docs must document machine-checked CORE/SHELL import direction") } }