Skip to content

Latest commit

 

History

History
219 lines (174 loc) · 9.52 KB

AGENTS.md

File metadata and controls

219 lines (174 loc) · 9.52 KB

Repository Guidelines

Project Structure & Modules

  • src/: Library source (TypeScript). Key areas:
    • core/: primitives and building blocks
      • guards: primitive.ts, object.ts
      • logic: logic.ts (and, andAll, or, not, guardIn)
      • equality: equals.ts (equals, equalsBy, equalsKey)
      • parsing: parse.ts (safeParse, safeParseWith)
      • nullability: nullish.ts (nullable, nonNull, nullish, optional, required)
      • helpers: define.ts, predicate.ts
      • combinators: core/combinators/* (arrayOf, tupleOf, oneOf, recordOf, struct, oneOfValues)
    • types/: shared type utilities
    • utils/: internal helpers (e.g., toBooleanPredicates)
    • Entry: src/index.ts re-exports public API (core, types, utils).
  • tests/: Jest runtime tests, mirroring src/ paths (e.g., tests/core/combinators/struct.test.ts).
  • tests-d/: tsd type tests, mirroring src/ paths (e.g., tests-d/core/combinators/struct.test-d.ts).
  • dist/: Build output (ESM .mjs, CJS .js, and .d.ts) generated by tsup.
  • Config: tsconfig*.json, tests/tsconfig.json, tests-d/tsconfig.json, tsup.config.ts, jest.config.cjs, .oxlintrc.json.

Build, Test, and Development

  • Install: pnpm install
  • Lint: pnpm lint — Oxlint over the repo.
  • Build: pnpm build — bundles via tsup to dist/ (ESM + CJS + types).
  • Test (runtime): pnpm test — runs Jest specs in tests/.
  • Test (types): pnpm test:types — runs tsd specs in tests-d/.

CI runs lint, build, unit tests, and type tests on PRs against main.

Coding Style & Naming

  • Language: TypeScript (strict: true). Target ES2022; outputs ESM/CJS.
  • Imports: use alias @/* for sources (e.g., import { struct } from '@/core/combinators').
  • Formatting: Oxfmt; 2-space indent; single quotes; semicolons.
  • Lint: Oxlint config in .oxlintrc.json. Project rules: complexity ≤ 7; no-unused-vars/no-undef warn; ignores dist/*.
  • Files: kebab-case for modules (core/combinators/tuple.ts), named exports; keep public API re-exported from src/index.ts.

Testing Guidelines

  • Frameworks: Jest for runtime behavior; tsd for type-level guarantees.
  • Naming: runtime tests tests/*.test.ts; type tests tests-d/*.test-d.ts.
  • Placement: mirror source path (e.g., src/core/combinators/record.ts → tests/record-of.test.ts).
  • Run locally: pnpm test && pnpm test:types before PR.

Type Tests (tsd) Style

  • Prefer semantic, readable grouping using comment-based suites:
    • Section header lines to mimic Jest describe/it hierarchy.
    • Pattern:
      // =============================================
      // describe: <topic>
      // =============================================
      // it: <expectation>
      // ... assertions via expectType<...>(...)
    • Keep one topic per section; add // Note: lines for caveats when helpful.
  • Keep imports at top; declare helper variables close to usage to aid narrowing demos.
  • If a single file grows large, it’s acceptable to split by topic (e.g., logic-and.test-d.ts, logic-or.test-d.ts).

tsd Project Config

  • Use a dedicated tests-d/tsconfig.json to give VSCode a clean project context:
    • module: NodeNext, moduleResolution: NodeNext, target: ES2022, strict: true, noEmit: true.
    • baseUrl: ".." and paths: { "@/*": ["src/*"] } to resolve source aliases.
    • Avoid types: ["node"] unless @types/node is installed; not required for current tests.
  • Runtime tests use tests/tsconfig.json extending root config and setting types: ["jest"].

Commit & Pull Requests

  • Commits: follow Conventional Commits observed in history: feat:, fix:, refactor:, test:, chore:. Keep scope concise (e.g., refactor: split combinators).
  • PRs: use template; include summary, rationale, and linked issues. Add screenshots if behavior changes.
  • Quality bar: ensure pnpm lint, pnpm build, pnpm test, and pnpm test:types pass locally.

Tips & Notes

  • Node: CI uses Node 22.22.0 and pnpm@11.2.2.
  • Public API: prefer adding exports in src/index.ts; keep internal helpers unexported.
  • New combinator example: add src/core/combinators/<name>.ts, export it in src/core/combinators/index.ts, add tests/<name>.test.ts and tests-d/<name>.test-d.ts.
  • struct supports { exact?: boolean } to disallow extra keys when exact: true.
  • Jest config maps @/* via moduleNameMapper and looks under tests/.

Implementation Style Notes

  • Prefer generics and proper refinements over assertion casts in public APIs.
    • Example (and):
      const and =
        <A, B extends A>(precondition: Guard<A>, condition: Refine<A, B>) =>
        (x: unknown): x is B =>
          precondition(x) && condition(x);
    • Rationale: preserves inference from the second predicate without as unknown as.
  • For collections of guards, convert to boolean predicates for iteration (or, etc.) via internal helpers (e.g., toBooleanPredicates).
  • Keep combinators total and side‑effect free; prefer linear scans for small literal sets (oneOfValues) and Set lookups for larger ones.

Documentation & Comments

  • Purpose: Add JSDoc to public APIs and WHY comments to non-obvious implementations. Leave enough intent so future readers don’t have to guess.
  • JSDoc: One-line summary + @param / @returns by default. Use @example only for broadly useful or commonly misused functions (optional). Do not use @typeParam.
  • WHY comments: Explain “why this design/implementation” briefly. Capture performance, compatibility, background, or constraints that are not obvious from code. Place directly above the target code.
  • Style: Concise, active voice, consistent terminology. Don’t restate information that TypeScript types already make obvious.

Example — logic combinator (public JSDoc):

/**
 * Combines a precondition guard with an additional refinement to narrow the type.
 * @param precondition Broad guard evaluated first; short-circuits on failure.
 * @param condition Refinement evaluated only when `precondition` passes.
 * @returns Predicate that narrows the input to a subtype.
 * // @example: include only if this utility is widely used or often misused
 */
export function and<A, B extends A>(
  precondition: Guard<A>,
  condition: Refine<A, B>
): (x: unknown) => x is B {
  /* ... */
}

Example — WHY comment (performance/semantics):

// WHY: For small literal sets, a linear scan with Object.is preserves exact
// equality semantics (NaN, +0/-0) without Set overhead. For larger sets,
// switch to Set membership for speed.
const ONE_OF_VALUES_LINEAR_SCAN_MAX = 8;

Intent/Background (Why)

Capture the reason this exists. If there’s time-bound context or deprecation intent, say so.

/**
 * Get congested time slots for the expo.
 * Background: Implemented for the 2025 Osaka Expo; scheduled for removal later.
 */
const getExpoConfusedTime = (targetDate: Date): Promise<Time[]> => {
  // ...
  return confusedTimeList;
};

Contract (JSDoc/TSDoc)

Document the interface of the function with @param / @returns. Add @example when it materially helps.

/**
 * Get congested time slots for the expo.
 * Background: Implemented for the 2025 Osaka Expo; scheduled for removal later.
 * @param targetDate Target date to retrieve congested time windows for.
 * @returns Array of congested time windows.
 */
const getExpoConfusedTime = (targetDate: Date): Promise<Time[]> => {
  /* ... */
};

Tagged comments

  • TODO: Work planned for later
  • FIXME: Known issue or temporary workaround
  • HACK: Necessary but non-ideal code
  • NOTE: Design rationale or reference info
/**
 * Get congested time slots for the expo.
 * Background: Implemented for the 2025 Osaka Expo.
 * TODO: Remove after the event ends.
 * @param targetDate Target date to retrieve congested time windows for.
 * @returns Array of congested time windows.
 */

Directive comments

When using oxlint-disable or @ts-ignore, always include a neighboring WHY comment explaining the suppression.

// HACK: Accept any due to unknown legacy type surface; tracked in issue #123.
// oxlint-disable-next-line typescript/no-explicit-any
function handleLegacyData(data: any) {
  // @ts-ignore: Third-party typings are outdated; safe in our usage context.
  someLegacyLibrary.doSomething(data);
}

Reference links

Link issues/PRs/specs with @see so readers can follow the context.

/**
 * Task executed during build.
 * Used to address the following issue.
 * @see https://github.com/your-org/your-repo/issues/123
 */

Scope guidelines:

  • Public exports: Always include JSDoc, and add WHY where non-obvious.
  • Internal utilities: Add brief JSDoc/WHY when used across modules or when behavior is tricky.
  • Avoid trivial inline comments; concentrate where they prevent misunderstandings or regressions.

Note: See .github/prompts/jsdoc.prompt.md for additional tone/phrasing inspiration.

Cursor Cloud specific instructions

  • Tool versions live in mise.toml (Node 22.22.0 and pnpm 11.2.2). Run mise exec -- pnpm … or mise run <task> so commands use that toolchain.
  • The docs site is pnpm --filter ./docs dev on port 3000. Open http://localhost:3000. Next.js 16 blocks dev-client resources for 127.0.0.1, so code tabs and other client UI do not hydrate on that host.
  • Docs pages import the workspace package from dist/. After changing src/, run mise exec -- pnpm build before checking the site.
  • next dev writes docs/AGENTS.md and docs/CLAUDE.md when they are missing. Those files are regenerated locally and are not part of the library API.