Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
28 changes: 27 additions & 1 deletion .claude/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,12 @@ bun test # all tests
bun test src/path/to/file.test.ts
bun run format # Prettier write — run before pushing
bun run format-check # CI runs this
bun run lint # oxlint + tsgolint type-aware rules; CI runs this
bun run lint-fix # oxlint --fix
bun run typecheck-tests # typecheck test files
```

CI runs `format-check` → `build` → `typecheck-tests` → `test`, cheapest first.
CI runs `format-check` → `lint` → `build` → `typecheck-tests` → `test`, cheapest first.

`typecheck-tests` is a separate script because test files are **excluded from the base
`tsconfig.json`** and vitest transpiles without typechecking — `build` and `test` both
Expand Down Expand Up @@ -325,6 +327,30 @@ From `.cursor/rules/`:
- `as any` only inside generic function bodies where TS cannot narrow
- Concise JSDoc only when behavior is non-obvious; `@link` for cross-references

## Linting

`oxlint` with `oxlint-tsgolint` for the type-aware rules, configured in
`.oxlintrc.json` and scoped to `src` and `scripts`. It replaces an
`eslint.config.js` that had been dead for a while: no `eslint` dependency was
installed, no script invoked it, and CI never ran it — so none of the rules it
named had ever fired. Several of the findings fixed on the way in were the
backlog that had built up behind that.

Type-aware rules need no build here: they resolve `@workglow/*` through the
published `dist/*.d.ts` that `bun install` already puts in `node_modules`.

Most type-aware rules are staged **off**, each with the count it reports today
written beside it in the config. They are real findings, not false positives,
and each is a cleanup of its own — `no-floating-promises` (22) is the one worth
doing first. `no-duplicate-type-constituents` is off permanently: it reports
`string | undefined` on optional parameters, which is the house style.

Gone with ESLint: `eslint-plugin-regexp`, which oxlint has no equivalent for.
`no-super-linear-backtracking` — the ReDoS guard — is the one with no
substitute; `no-control-regex`, `no-invalid-regexp`,
`no-misleading-character-class` and `no-useless-backreference` all survive in
oxlint's `correctness` set.

## Formatting

Prettier: 100 char width, 2-space indent, double quotes, trailing commas (es5), semicolons.
Expand Down
4 changes: 4 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ jobs:
bun-version: latest
- run: bun i
- run: bun run format-check
# oxlint + tsgolint. Needs no build of its own: the type-aware rules
# resolve `@workglow/*` through the published `dist/*.d.ts` that `bun i`
# already put in node_modules.
- run: bun run lint
- run: bun run build
- run: bun run typecheck-tests
- run: bun run test
73 changes: 73 additions & 0 deletions .oxlintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
{
"$schema": "./node_modules/oxlint/configuration_schema.json",

// The plugin set the old ESLint config named, minus the ones oxlint has no
// counterpart for. `plugins` REPLACES oxlint's defaults, so `unicorn` and
// `oxc` are off: they are rule families this package has never linted
// against, and adopting them is worth doing on its own rather than as a side
// effect of wiring up a linter.
//
// `eslint-plugin-regexp` has no oxlint equivalent and is gone. `correctness`
// still covers `no-control-regex`, `no-invalid-regexp`,
// `no-misleading-character-class` and `no-useless-backreference`; the
// optimisation and catastrophic-backtracking rules have no replacement.
//
// `react` and `jsx-a11y` are here because the old config listed them. Nothing
// in this package renders JSX today; they cost nothing and hold the line if
// the web console ever grows components here.
"plugins": ["typescript", "react", "jsx-a11y"],

"categories": { "correctness": "error" },

"env": {
"browser": true,
"es2024": true,
"node": true
},

// `.claude/skills/**` is vendored skill tooling, not this package's source.
"ignorePatterns": ["dist", "node_modules", ".claude"],

"rules": {
// Enabled by the old config's `typescript-eslint/recommended` but not by
// oxlint's `correctness`.
"typescript/ban-ts-comment": "error",
"typescript/no-array-constructor": "error",
"typescript/no-empty-object-type": "error",
"typescript/no-namespace": "error",
"typescript/no-require-imports": "error",
"typescript/no-unnecessary-type-constraint": "error",
"typescript/no-unsafe-function-type": "error",

// Off in the old config, for the reasons the code style gives:
// `noUnusedLocals` already covers the first, and `as any` inside generic
// function bodies is sanctioned.
"no-unused-vars": "off",
"typescript/no-explicit-any": "off",

// `TableRenderer`'s invisible-whitespace class is written entirely in
// `\uXXXX` escapes, and two adjacent escapes there read as one combining
// sequence. Spelling those code points literally to satisfy the default is
// exactly the unreadable form the escapes exist to avoid.
"no-misleading-character-class": ["error", { "allowEscape": true }],

// ---------------------------------------------------------------------
// Type-aware rules run only under `oxlint --type-aware`, which shells out
// to tsgolint. Everything type-aware in `correctness` is on except these,
// which the tree does not pass yet; the counts are what they report today.
// They are real findings rather than false positives, and each is a
// cleanup of its own rather than part of getting a linter running.
// ---------------------------------------------------------------------
"typescript/no-floating-promises": "off", // 22
"typescript/no-base-to-string": "off", // 10
"typescript/no-useless-default-assignment": "off", // 6
"typescript/require-array-sort-compare": "off", // 5
"typescript/no-redundant-type-constituents": "off", // 3
"typescript/restrict-template-expressions": "off", // 3
"typescript/unbound-method": "off", // 3
// Reports "explicit undefined is unnecessary on an optional parameter",
// which is the house style (`string | undefined` over `?: string`) rather
// than a defect. Off for good, not pending a cleanup.
"typescript/no-duplicate-type-constituents": "off"
}
}
3 changes: 2 additions & 1 deletion .prettierrc
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,6 @@
"singleQuote": false,
"trailingComma": "es5",
"bracketSpacing": true,
"arrowParens": "always"
"arrowParens": "always",
"plugins": ["@sroussey/prettier-plugin-organize-imports"]
}
13 changes: 7 additions & 6 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -202,8 +202,8 @@ Define the TypeBox schema that mirrors the XML structure of the SEC filing.
```typescript
// src/sec/forms/<category>/Form_X.schema.ts

import { Type, Static } from "typebox";
import {} from /* reusable types */ "../FormSchemaUtil";
import { Static, Type } from "typebox";
import /* reusable types */ "../FormSchemaUtil";

// Define sub-types for nested XML elements
const SOME_NESTED_TYPE = Type.Object({
Expand Down Expand Up @@ -277,9 +277,9 @@ This file transforms the parsed form data into normalized records and saves them
```typescript
// src/sec/forms/<category>/Form_X.storage.ts

import { AddressRepo } from "../../../storage/address/AddressRepo";
import { CompanyRepo } from "../../../storage/company/CompanyRepo";
import { PersonRepo } from "../../../storage/person/PersonRepo";
import { AddressRepo } from "../../../storage/address/AddressRepo";
import { PhoneRepo } from "../../../storage/phone/PhoneRepo";
// ... import other repos as needed
import { FormX } from "./Form_X.schema";
Expand Down Expand Up @@ -368,13 +368,14 @@ If the form contains data that doesn't fit into the existing `person/company/add
```typescript
// src/sec/forms/<category>/Form_X.test.ts

import { beforeEach, describe, expect, it } from "vitest";
import { readFileSync } from "fs";
import { join } from "path";
import { Form_X } from "./Form_X";
import { processFormX } from "./Form_X.storage";
import { beforeEach, describe, expect, it } from "vitest";
import { resetDependencyInjectionsForTesting } from "../../../config/TestingDI";
import { PersonRepo } from "../../../storage/person/PersonRepo";
import { Form_X } from "./Form_X";
import { processFormX } from "./Form_X.storage";

// ... import other repos

describe("Form_X", () => {
Expand Down
Loading