feat(ts): add real quality gates — Biome lint + effect-law ratchet + class inventory
- biome.json (2.4.16, linter-only) wired as "lint" in all six packages
- scripts/check-effect-laws.ts: Effect-native law enforcement encoding the
adapted beep-effect effect-first/schema-first laws (no native JSON/switch/
sort/fetch/timers, no process.env, no throw new, no Effect.run* outside
boundaries, no Schema-suffixed constants, no node:fs/path, AST-based
pure-data interface detection per law 38/39)
- ratcheting baseline allowlist (95 entries / 290 findings) that must shrink
to documented exemptions only; stale counts fail the gate
- root lint chains turbo lint + law check + native-class inventory
- fix all 163 initial Biome findings: import-type style, templates, two `any`s,
ten non-null assertions (librarian getService gate, A.matchRight in atoms,
ensureNode returning nodes, main.tsx mount guard)
Gates: lint, check:tsgo, build, test (force, 11 tasks) all green.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 06:40:01 -05:00
|
|
|
|
/**
|
|
|
|
|
|
* Effect-native law enforcement for the TrustGraph TS port.
|
|
|
|
|
|
*
|
|
|
|
|
|
* Encodes the adapted beep-effect effect-first/schema-first laws as regex and
|
|
|
|
|
|
* AST sweeps over packages/*\/src and scripts/, with a ratcheting baseline:
|
|
|
|
|
|
* every violation must be covered by an exact-count baseline entry or a
|
|
|
|
|
|
* reasoned permanent exemption in scripts/effect-laws.allowlist.json. Counts
|
|
|
|
|
|
* that drop force a baseline update (ratchet down); counts that grow fail.
|
|
|
|
|
|
*
|
|
|
|
|
|
* Usage:
|
|
|
|
|
|
* bun scripts/check-effect-laws.ts # verify, exit 1 on drift
|
|
|
|
|
|
* bun scripts/check-effect-laws.ts --write-baseline # regenerate baseline section
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
import * as BunFileSystem from "@effect/platform-bun/BunFileSystem";
|
|
|
|
|
|
import * as BunRuntime from "@effect/platform-bun/BunRuntime";
|
|
|
|
|
|
import { Effect } from "effect";
|
|
|
|
|
|
import * as A from "effect/Array";
|
|
|
|
|
|
import * as Console from "effect/Console";
|
|
|
|
|
|
import * as FileSystem from "effect/FileSystem";
|
|
|
|
|
|
import * as O from "effect/Option";
|
|
|
|
|
|
import * as S from "effect/Schema";
|
|
|
|
|
|
import * as Str from "effect/String";
|
|
|
|
|
|
import ts from "typescript";
|
|
|
|
|
|
|
|
|
|
|
|
const ALLOWLIST_PATH = "scripts/effect-laws.allowlist.json";
|
|
|
|
|
|
const SELF_PATH = "scripts/check-effect-laws.ts";
|
|
|
|
|
|
|
|
|
|
|
|
class LawCheckFailed extends S.TaggedErrorClass<LawCheckFailed>()(
|
|
|
|
|
|
"LawCheckFailed",
|
|
|
|
|
|
{ message: S.String },
|
|
|
|
|
|
) {}
|
|
|
|
|
|
|
|
|
|
|
|
class BaselineEntry extends S.Class<BaselineEntry>("BaselineEntry")({
|
|
|
|
|
|
rule: S.String,
|
|
|
|
|
|
path: S.String,
|
|
|
|
|
|
count: S.Number,
|
|
|
|
|
|
}) {}
|
|
|
|
|
|
|
|
|
|
|
|
class Exemption extends S.Class<Exemption>("Exemption")({
|
|
|
|
|
|
rule: S.String,
|
|
|
|
|
|
path: S.String,
|
|
|
|
|
|
reason: S.String,
|
|
|
|
|
|
}) {}
|
|
|
|
|
|
|
|
|
|
|
|
class Allowlist extends S.Class<Allowlist>("Allowlist")({
|
|
|
|
|
|
exemptions: S.Array(Exemption),
|
|
|
|
|
|
baseline: S.Array(BaselineEntry),
|
|
|
|
|
|
}) {}
|
|
|
|
|
|
|
|
|
|
|
|
const AllowlistJson = S.fromJsonString(Allowlist);
|
|
|
|
|
|
const decodeAllowlist = S.decodeUnknownEffect(AllowlistJson);
|
|
|
|
|
|
const encodeAllowlist = S.encodeUnknownEffect(AllowlistJson);
|
|
|
|
|
|
|
|
|
|
|
|
type RuleScope = "prod" | "all";
|
|
|
|
|
|
|
|
|
|
|
|
interface RegexRule {
|
|
|
|
|
|
readonly id: string;
|
|
|
|
|
|
readonly description: string;
|
|
|
|
|
|
readonly scope: RuleScope;
|
|
|
|
|
|
readonly pattern: RegExp;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
interface Violation {
|
|
|
|
|
|
readonly rule: string;
|
|
|
|
|
|
readonly path: string;
|
|
|
|
|
|
readonly line: number;
|
|
|
|
|
|
readonly excerpt: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const regexRules: ReadonlyArray<RegexRule> = [
|
|
|
|
|
|
{
|
|
|
|
|
|
id: "no-native-json",
|
|
|
|
|
|
description: "Use Schema JSON codecs (S.fromJsonString / S.UnknownFromJsonString), not native JSON",
|
|
|
|
|
|
scope: "all",
|
|
|
|
|
|
pattern: /JSON\.(parse|stringify)\(/,
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
id: "no-process-env",
|
|
|
|
|
|
description: "Read configuration through Config / ConfigProvider, not process.env",
|
|
|
|
|
|
scope: "all",
|
|
|
|
|
|
pattern: /process\.env/,
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
id: "no-error-throw",
|
|
|
|
|
|
description: "Fail with tagged errors on the Effect channel, not thrown native Error",
|
|
|
|
|
|
scope: "all",
|
|
|
|
|
|
pattern: /\bthrow\s+new\s|\bnew Error\(/,
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
id: "no-native-switch",
|
|
|
|
|
|
description: "Branch with effect/Match or schema tagged-union matchers, not native switch",
|
|
|
|
|
|
scope: "all",
|
|
|
|
|
|
pattern: /\bswitch\s*\(/,
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
id: "no-native-sort",
|
|
|
|
|
|
description: "Sort with A.sort and an explicit Order, not Array.prototype.sort",
|
|
|
|
|
|
scope: "all",
|
|
|
|
|
|
pattern: /\.sort\(\s*[)(]/,
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
id: "no-effect-run",
|
|
|
|
|
|
description: "Run effects only at process/test boundaries (runMain); libraries return Effect",
|
|
|
|
|
|
scope: "all",
|
|
|
|
|
|
pattern: /Effect\.run(Sync|Promise|Fork)\b/,
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
id: "no-schema-suffix",
|
|
|
|
|
|
description: "Schema constants carry the domain name, never a Schema suffix",
|
|
|
|
|
|
scope: "all",
|
|
|
|
|
|
pattern: /export const \w+Schema\b/,
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
id: "no-node-fs-path",
|
|
|
|
|
|
description: "Use effect/FileSystem and effect/Path services, not node:fs / node:path",
|
|
|
|
|
|
scope: "all",
|
|
|
|
|
|
pattern: /from\s+"node:(fs|path)"/,
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
id: "no-native-fetch",
|
|
|
|
|
|
description: "Use HttpClient from effect/unstable/http with a platform layer, not fetch",
|
|
|
|
|
|
scope: "all",
|
|
|
|
|
|
pattern: /\bfetch\s*\(/,
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
id: "no-ts-escape",
|
|
|
|
|
|
description: "No any/test-escape hatches in source",
|
|
|
|
|
|
scope: "all",
|
|
|
|
|
|
pattern: /@ts-ignore|@ts-expect-error|\bas any\b/,
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
id: "no-native-timers",
|
|
|
|
|
|
description: "Model time with Effect.sleep / Schedule / Duration, not setTimeout/setInterval",
|
|
|
|
|
|
scope: "all",
|
|
|
|
|
|
pattern: /\bset(Timeout|Interval)\(/,
|
|
|
|
|
|
},
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
const SCHEMA_FIRST_RULE = "schema-first-data";
|
|
|
|
|
|
|
|
|
|
|
|
const sourceFilePattern = /\.tsx?$/;
|
|
|
|
|
|
|
|
|
|
|
|
const isExcludedDir = (segment: string): boolean =>
|
|
|
|
|
|
segment === "dist" || segment === "node_modules" || segment === ".turbo" || segment === "__tests__";
|
|
|
|
|
|
|
|
|
|
|
|
const isTestFile = (path: string): boolean =>
|
|
|
|
|
|
path.includes("/__tests__/") ||
|
|
|
|
|
|
/\.(test|spec)\.tsx?$/.test(path);
|
|
|
|
|
|
|
|
|
|
|
|
const isProdSource = (path: string): boolean =>
|
|
|
|
|
|
path.startsWith("packages/") && path.includes("/src/") && !isTestFile(path);
|
|
|
|
|
|
|
|
|
|
|
|
const isScriptSource = (path: string): boolean => path.startsWith("scripts/");
|
|
|
|
|
|
|
|
|
|
|
|
const collectSourceFiles = Effect.fn("LawCheck.collectSourceFiles")(function* (
|
|
|
|
|
|
fs: FileSystem.FileSystem,
|
|
|
|
|
|
) {
|
|
|
|
|
|
const entries = yield* fs.readDirectory(".", { recursive: true });
|
|
|
|
|
|
return A.filter(
|
|
|
|
|
|
entries,
|
|
|
|
|
|
(path) =>
|
|
|
|
|
|
sourceFilePattern.test(path) &&
|
|
|
|
|
|
!path.endsWith(".d.ts") &&
|
|
|
|
|
|
path !== SELF_PATH &&
|
|
|
|
|
|
!A.some(path.split("/"), isExcludedDir) &&
|
|
|
|
|
|
!isTestFile(path) &&
|
|
|
|
|
|
(isProdSource(path) || isScriptSource(path)),
|
|
|
|
|
|
);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
const scanRegexRules = (path: string, text: string): ReadonlyArray<Violation> => {
|
|
|
|
|
|
const prod = isProdSource(path);
|
|
|
|
|
|
const lines = text.split("\n");
|
|
|
|
|
|
return regexRules
|
|
|
|
|
|
.filter((rule) => rule.scope === "all" || prod)
|
|
|
|
|
|
.flatMap((rule) =>
|
|
|
|
|
|
lines.flatMap((lineText, index) =>
|
|
|
|
|
|
rule.pattern.test(lineText)
|
|
|
|
|
|
? [{
|
|
|
|
|
|
rule: rule.id,
|
|
|
|
|
|
path,
|
|
|
|
|
|
line: index + 1,
|
|
|
|
|
|
excerpt: Str.trim(lineText).slice(0, 120),
|
|
|
|
|
|
}]
|
|
|
|
|
|
: [],
|
|
|
|
|
|
),
|
|
|
|
|
|
);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Law 38/39 split: an exported interface or type literal whose members are all
|
|
|
|
|
|
* non-function property signatures is a pure-data model and must be a Schema.
|
|
|
|
|
|
* Members with call/method/construct/index signatures or function-typed
|
|
|
|
|
|
* properties mark a service contract, which may stay an interface.
|
|
|
|
|
|
*/
|
|
|
|
|
|
const scanSchemaFirst = (path: string, text: string): ReadonlyArray<Violation> => {
|
|
|
|
|
|
if (!isProdSource(path) || path.endsWith(".tsx")) return [];
|
|
|
|
|
|
|
|
|
|
|
|
const source = ts.createSourceFile(path, text, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
|
|
|
|
|
|
const violations: Violation[] = [];
|
|
|
|
|
|
|
|
|
|
|
|
const isExported = (node: ts.HasModifiers): boolean =>
|
|
|
|
|
|
ts.getModifiers(node)?.some((modifier) => modifier.kind === ts.SyntaxKind.ExportKeyword) ?? false;
|
|
|
|
|
|
|
2026-06-11 07:37:59 -05:00
|
|
|
|
// A same-name const (schema value + type merge, e.g. recursive S.suspend
|
|
|
|
|
|
// schemas) means the schema already exists; the companion type is required.
|
|
|
|
|
|
const constNames = new Set<string>();
|
|
|
|
|
|
for (const statement of source.statements) {
|
|
|
|
|
|
if (ts.isVariableStatement(statement)) {
|
|
|
|
|
|
for (const declaration of statement.declarationList.declarations) {
|
|
|
|
|
|
if (ts.isIdentifier(declaration.name)) constNames.add(declaration.name.text);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
feat(ts): add real quality gates — Biome lint + effect-law ratchet + class inventory
- biome.json (2.4.16, linter-only) wired as "lint" in all six packages
- scripts/check-effect-laws.ts: Effect-native law enforcement encoding the
adapted beep-effect effect-first/schema-first laws (no native JSON/switch/
sort/fetch/timers, no process.env, no throw new, no Effect.run* outside
boundaries, no Schema-suffixed constants, no node:fs/path, AST-based
pure-data interface detection per law 38/39)
- ratcheting baseline allowlist (95 entries / 290 findings) that must shrink
to documented exemptions only; stale counts fail the gate
- root lint chains turbo lint + law check + native-class inventory
- fix all 163 initial Biome findings: import-type style, templates, two `any`s,
ten non-null assertions (librarian getService gate, A.matchRight in atoms,
ensureNode returning nodes, main.tsx mount guard)
Gates: lint, check:tsgo, build, test (force, 11 tasks) all green.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 06:40:01 -05:00
|
|
|
|
const isFunctionType = (type: ts.TypeNode | undefined): boolean =>
|
|
|
|
|
|
type !== undefined && (ts.isFunctionTypeNode(type) || ts.isConstructorTypeNode(type));
|
|
|
|
|
|
|
2026-06-11 07:37:59 -05:00
|
|
|
|
// Capability-typed members (Effects, Streams, schema codecs, layers,
|
|
|
|
|
|
// backends/services) mark a contract, not a data model.
|
|
|
|
|
|
const capabilityTypePattern =
|
|
|
|
|
|
/\b(Effect|Stream|Layer|Scope|Fiber|Queue|PubSub|Deferred|Ref|SubscriptionRef|SynchronizedRef|Codec|Schema|Context|Runtime)\s*[.<]|\b\w*(Backend|Service|Producer|Consumer|Requestor|Client|Factory|RequestResponse)\b/;
|
|
|
|
|
|
|
|
|
|
|
|
const isCapabilityType = (type: ts.TypeNode | undefined): boolean =>
|
|
|
|
|
|
type !== undefined && capabilityTypePattern.test(type.getText(source));
|
|
|
|
|
|
|
feat(ts): add real quality gates — Biome lint + effect-law ratchet + class inventory
- biome.json (2.4.16, linter-only) wired as "lint" in all six packages
- scripts/check-effect-laws.ts: Effect-native law enforcement encoding the
adapted beep-effect effect-first/schema-first laws (no native JSON/switch/
sort/fetch/timers, no process.env, no throw new, no Effect.run* outside
boundaries, no Schema-suffixed constants, no node:fs/path, AST-based
pure-data interface detection per law 38/39)
- ratcheting baseline allowlist (95 entries / 290 findings) that must shrink
to documented exemptions only; stale counts fail the gate
- root lint chains turbo lint + law check + native-class inventory
- fix all 163 initial Biome findings: import-type style, templates, two `any`s,
ten non-null assertions (librarian getService gate, A.matchRight in atoms,
ensureNode returning nodes, main.tsx mount guard)
Gates: lint, check:tsgo, build, test (force, 11 tasks) all green.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 06:40:01 -05:00
|
|
|
|
const isPureDataMembers = (members: ts.NodeArray<ts.TypeElement>): boolean =>
|
|
|
|
|
|
members.length > 0 &&
|
|
|
|
|
|
members.every(
|
|
|
|
|
|
(member) =>
|
|
|
|
|
|
ts.isPropertySignature(member) &&
|
2026-06-11 07:37:59 -05:00
|
|
|
|
!isFunctionType(member.type) &&
|
|
|
|
|
|
!isCapabilityType(member.type),
|
feat(ts): add real quality gates — Biome lint + effect-law ratchet + class inventory
- biome.json (2.4.16, linter-only) wired as "lint" in all six packages
- scripts/check-effect-laws.ts: Effect-native law enforcement encoding the
adapted beep-effect effect-first/schema-first laws (no native JSON/switch/
sort/fetch/timers, no process.env, no throw new, no Effect.run* outside
boundaries, no Schema-suffixed constants, no node:fs/path, AST-based
pure-data interface detection per law 38/39)
- ratcheting baseline allowlist (95 entries / 290 findings) that must shrink
to documented exemptions only; stale counts fail the gate
- root lint chains turbo lint + law check + native-class inventory
- fix all 163 initial Biome findings: import-type style, templates, two `any`s,
ten non-null assertions (librarian getService gate, A.matchRight in atoms,
ensureNode returning nodes, main.tsx mount guard)
Gates: lint, check:tsgo, build, test (force, 11 tasks) all green.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 06:40:01 -05:00
|
|
|
|
);
|
|
|
|
|
|
|
2026-06-11 07:37:59 -05:00
|
|
|
|
// Schemas cannot be generic; generic shapes stay structural types.
|
|
|
|
|
|
const isGeneric = (typeParameters: ts.NodeArray<ts.TypeParameterDeclaration> | undefined): boolean =>
|
|
|
|
|
|
typeParameters !== undefined && typeParameters.length > 0;
|
|
|
|
|
|
|
|
|
|
|
|
// Law 38/39 split: constructor/function option bags stay interfaces.
|
|
|
|
|
|
const isOptionBagName = (name: string): boolean => /Options$/.test(name);
|
|
|
|
|
|
|
feat(ts): add real quality gates — Biome lint + effect-law ratchet + class inventory
- biome.json (2.4.16, linter-only) wired as "lint" in all six packages
- scripts/check-effect-laws.ts: Effect-native law enforcement encoding the
adapted beep-effect effect-first/schema-first laws (no native JSON/switch/
sort/fetch/timers, no process.env, no throw new, no Effect.run* outside
boundaries, no Schema-suffixed constants, no node:fs/path, AST-based
pure-data interface detection per law 38/39)
- ratcheting baseline allowlist (95 entries / 290 findings) that must shrink
to documented exemptions only; stale counts fail the gate
- root lint chains turbo lint + law check + native-class inventory
- fix all 163 initial Biome findings: import-type style, templates, two `any`s,
ten non-null assertions (librarian getService gate, A.matchRight in atoms,
ensureNode returning nodes, main.tsx mount guard)
Gates: lint, check:tsgo, build, test (force, 11 tasks) all green.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 06:40:01 -05:00
|
|
|
|
const record = (node: ts.Node, name: string): void => {
|
|
|
|
|
|
const position = source.getLineAndCharacterOfPosition(node.getStart(source));
|
|
|
|
|
|
violations.push({
|
|
|
|
|
|
rule: SCHEMA_FIRST_RULE,
|
|
|
|
|
|
path,
|
|
|
|
|
|
line: position.line + 1,
|
|
|
|
|
|
excerpt: `exported pure-data shape ${name} should be an effect/Schema model`,
|
|
|
|
|
|
});
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const visit = (node: ts.Node): void => {
|
2026-06-11 07:37:59 -05:00
|
|
|
|
if (
|
|
|
|
|
|
ts.isInterfaceDeclaration(node) &&
|
|
|
|
|
|
isExported(node) &&
|
|
|
|
|
|
!isGeneric(node.typeParameters) &&
|
|
|
|
|
|
node.heritageClauses === undefined &&
|
|
|
|
|
|
isPureDataMembers(node.members) &&
|
|
|
|
|
|
!constNames.has(node.name.text) &&
|
|
|
|
|
|
!isOptionBagName(node.name.text)
|
|
|
|
|
|
) {
|
feat(ts): add real quality gates — Biome lint + effect-law ratchet + class inventory
- biome.json (2.4.16, linter-only) wired as "lint" in all six packages
- scripts/check-effect-laws.ts: Effect-native law enforcement encoding the
adapted beep-effect effect-first/schema-first laws (no native JSON/switch/
sort/fetch/timers, no process.env, no throw new, no Effect.run* outside
boundaries, no Schema-suffixed constants, no node:fs/path, AST-based
pure-data interface detection per law 38/39)
- ratcheting baseline allowlist (95 entries / 290 findings) that must shrink
to documented exemptions only; stale counts fail the gate
- root lint chains turbo lint + law check + native-class inventory
- fix all 163 initial Biome findings: import-type style, templates, two `any`s,
ten non-null assertions (librarian getService gate, A.matchRight in atoms,
ensureNode returning nodes, main.tsx mount guard)
Gates: lint, check:tsgo, build, test (force, 11 tasks) all green.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 06:40:01 -05:00
|
|
|
|
record(node, node.name.text);
|
|
|
|
|
|
}
|
|
|
|
|
|
if (
|
|
|
|
|
|
ts.isTypeAliasDeclaration(node) &&
|
|
|
|
|
|
isExported(node) &&
|
2026-06-11 07:37:59 -05:00
|
|
|
|
!isGeneric(node.typeParameters) &&
|
feat(ts): add real quality gates — Biome lint + effect-law ratchet + class inventory
- biome.json (2.4.16, linter-only) wired as "lint" in all six packages
- scripts/check-effect-laws.ts: Effect-native law enforcement encoding the
adapted beep-effect effect-first/schema-first laws (no native JSON/switch/
sort/fetch/timers, no process.env, no throw new, no Effect.run* outside
boundaries, no Schema-suffixed constants, no node:fs/path, AST-based
pure-data interface detection per law 38/39)
- ratcheting baseline allowlist (95 entries / 290 findings) that must shrink
to documented exemptions only; stale counts fail the gate
- root lint chains turbo lint + law check + native-class inventory
- fix all 163 initial Biome findings: import-type style, templates, two `any`s,
ten non-null assertions (librarian getService gate, A.matchRight in atoms,
ensureNode returning nodes, main.tsx mount guard)
Gates: lint, check:tsgo, build, test (force, 11 tasks) all green.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 06:40:01 -05:00
|
|
|
|
ts.isTypeLiteralNode(node.type) &&
|
2026-06-11 07:37:59 -05:00
|
|
|
|
isPureDataMembers(node.type.members) &&
|
|
|
|
|
|
!constNames.has(node.name.text) &&
|
|
|
|
|
|
!isOptionBagName(node.name.text)
|
feat(ts): add real quality gates — Biome lint + effect-law ratchet + class inventory
- biome.json (2.4.16, linter-only) wired as "lint" in all six packages
- scripts/check-effect-laws.ts: Effect-native law enforcement encoding the
adapted beep-effect effect-first/schema-first laws (no native JSON/switch/
sort/fetch/timers, no process.env, no throw new, no Effect.run* outside
boundaries, no Schema-suffixed constants, no node:fs/path, AST-based
pure-data interface detection per law 38/39)
- ratcheting baseline allowlist (95 entries / 290 findings) that must shrink
to documented exemptions only; stale counts fail the gate
- root lint chains turbo lint + law check + native-class inventory
- fix all 163 initial Biome findings: import-type style, templates, two `any`s,
ten non-null assertions (librarian getService gate, A.matchRight in atoms,
ensureNode returning nodes, main.tsx mount guard)
Gates: lint, check:tsgo, build, test (force, 11 tasks) all green.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 06:40:01 -05:00
|
|
|
|
) {
|
|
|
|
|
|
record(node, node.name.text);
|
|
|
|
|
|
}
|
|
|
|
|
|
ts.forEachChild(node, visit);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
visit(source);
|
|
|
|
|
|
return violations;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const countKey = (rule: string, path: string): string => `${rule} |