Cursor Rules for TypeScript Monorepos: Make AI Edits Stick

Cursor Rules for TypeScript Monorepos: Make AI Edits Stick

AI coding assistants will cheerfully invent a fourth state library, import across package boundaries, and “fix” a bug by silencing TypeScript. In a monorepo that is expensive. Cursor rules (project rules / .cursor/rules / AGENTS.md-style guidance the agent actually reads) are how you turn tribal knowledge into machine-enforceable defaults: package boundaries, test commands, API patterns, and “never do this” lists. The unfair advantage is not a longer prompt — it is rules that match CI, so edits survive review.

⚡ TL;DR: Put durable engineering law in versioned Cursor project rules scoped by glob (apps vs packages vs infra). Encode package boundaries (no cross-imports except via public barrels), required commands (pnpm lint, pnpm test -w @scope/foo), and architectural do/don’ts. Keep secrets out of rules; point agents at runbooks. Pair rules with CI identical to what you tell the agent. Illustrative win: cut “agent PR thrash” cycles by making the first patch already pass tsc -b and package tests.

Why monorepos punish unconstrained agents

A single-repo Node app can absorb messy imports. A pnpm/turbo/nx workspace cannot:

  • Agents add dependencies to the wrong package.json
  • They reach into packages/ui/src/internal instead of the public export map
  • They duplicate types already in packages/contracts
  • They “fix” CI by widening any or disabling eslint on a line

Cursor rules shrink the search space. Treat them like lint for the agent’s planning.

<!-- .cursor/rules/monorepo-core.mdc (illustrative shape) -->
---
description: Core TypeScript monorepo laws for CheatCoders-style workspaces
globs: ["**/*.{ts,tsx}"]
alwaysApply: true
---

# Monorepo core

- Package manager: **pnpm** workspaces. Never suggest npm/yarn install at repo root.
- TypeScript project references: run `pnpm exec tsc -b` after cross-package edits.
- Imports: only via package name (`@acme/ui`) or relative within the same package.
- ❌ No deep imports into another package’s `src/`.
- ✅ Add deps with `pnpm add <pkg> --filter @acme/<name>`.
- Prefer editing existing abstractions over inventing parallel utilities.

✅ Rules that name commands that exist in package.json.
❌ Vague “write clean code” slogans the model already heard a thousand times.

Scope rules by glob: apps, packages, infra

One giant rule file becomes noise. Split by blast radius:

.cursor/rules/
  00-monorepo-core.mdc          # alwaysApply
  10-packages-ui.mdc            # globs: packages/ui/**
  20-apps-web.mdc               # globs: apps/web/**
  30-functions-aws.mdc          # globs: packages/functions/**, infra/**
  40-testing.mdc                # globs: **/*.{test,spec}.ts*
---
description: AWS Lambda package conventions
globs: ["packages/functions/**", "infra/**"]
---

# Lambda packages

- Runtime: Node 20, ARM64 preferred when deps allow.
- Observability: use Lambda Powertools Logger/Metrics/Tracer patterns already in `packages/functions/src/observability.ts`.
- ❌ Don’t log Authorization headers or raw PII.
- ✅ Idempotency for SQS consumers (see internal doc link / sibling package).
- Bundling: esbuild via the existing `pnpm --filter @acme/functions build` pipeline — don’t invent a second bundler.
- Timeouts: set SDK client timeouts under function timeout; see production runbook.

That AWS-flavored rule is optional but natural when the monorepo ships Lambdas — align with LLM Coding Agents on AWS: Safe Tool Sandboxes with Lambda and AI Code Review Bots: IAM, Secrets, and Least-Privilege Pipelines so local agent edits do not fight your PR bot.

Encode boundaries in both rules and tooling

Rules without enforcement are fanfic. Mirror them in ESLint/dependency-cruiser/nx tags:

// eslint.config.js — illustrative boundary (eslint-plugin-import or @nx/enforce-module-boundaries)
export default [
  {
    files: ["packages/ui/**/*.{ts,tsx}"],
    rules: {
      "no-restricted-imports": [
        "error",
        {
          patterns: [
            {
              group: ["@acme/api/*", "**/apps/**"],
              message: "UI cannot import apps or API internals. Use @acme/contracts.",
            },
          ],
        },
      ],
    },
  },
];
// packages/ui/package.json — exports map is the public API contract
{
  "name": "@acme/ui",
  "exports": {
    ".": "./dist/index.js",
    "./styles.css": "./dist/styles.css"
  },
  "types": "./dist/index.d.ts"
}

Tell Cursor explicitly:

# Public API

- When adding a UI component, export it from `packages/ui/src/index.ts` and rebuild types.
- Consumers must import `@acme/ui`, never `@acme/ui/src/Button`.
- If you need a new cross-package type, put it in `@acme/contracts` first.

Make “done” mean green: commands in the rule

Agents stop early when “done” is undefined. Put the acceptance commands in the rule file:

## Definition of done for TS changes

1. `pnpm exec prettier --write` on touched files (or project format script).
2. `pnpm exec tsc -b --pretty false` at repo root — zero errors.
3. Tests: `pnpm --filter <affected> test` for every package you changed.
4. If you touched `packages/functions`, run the package’s `test:unit` and do **not** deploy from the agent.
5. Summarize: packages touched, commands run, residual risks.

Optional helper script the agent can call:

// tools/affected-check.ts — thin wrapper you maintain; agent runs it
import { execSync } from "node:child_process";

const filters = process.argv.slice(2);
if (!filters.length) {
  console.error("usage: affected-check @acme/ui @acme/web");
  process.exit(2);
}
for (const f of filters) {
  console.log(`\n=== ${f} ===`);
  execSync(`pnpm --filter ${f} lint`, { stdio: "inherit" });
  execSync(`pnpm --filter ${f} test`, { stdio: "inherit" });
}
execSync("pnpm exec tsc -b", { stdio: "inherit" });

✅ Agent runs your script.
❌ Agent improvises npx jest with a different config.

Prompt patterns that stick (without fighting the rules)

In chat, still be specific — rules are defaults, not mind readers:

Implement rate-limit headers on apps/web API routes.
Follow .cursor rules. Only touch apps/web and @acme/contracts if types change.
Run the definition-of-done commands before summarizing.
Do not add new dependencies unless required; prefer existing middleware.

For refactors, forbid scope creep:

Move `formatMoney` from apps/web/lib to @acme/contracts.
Update imports. No behavior change. No new abstractions.

When the agent proposes AWS changes, point it at existing IAM least-privilege patterns from AI Code Review Bots: IAM, Secrets, and Least-Privilege Pipelines and sandboxing from LLM Coding Agents on AWS — do not let it mint *:* “so the demo works.”

Keep rules maintainable

  • Version rules with the repo — PR review for rule changes like any other policy.
  • Delete stale rules — wrong guidance is worse than none.
  • Never put secrets / prod URLs with tokens in rules.
  • Link to canonical docs instead of pasting 2k-line style guides.
  • Rehearse quarterly: break a boundary on purpose; confirm eslint + agent both catch it.
## Anti-patterns for this repo

- ❌ Disabling eslint with wide `any` to silence TS
- ❌ Adding Redux/Zustand/Jotai when React Query + context already covers the case
- ❌ Creating `utils2.ts` instead of extending existing helpers
- ❌ Editing generated code under `**/generated/**`
- ✅ Prefer small PRs: one package’s public API change + follow-up consumer PR if needed

Bedrock/tool agents in your cloud account still need runtime guardrails (Bedrock Agents: Tool Use, Memory, and Production Guardrails); Cursor rules are the dev-time cousin — complementary, not interchangeable.

Closing checklist

✅ Dos
– ✅ Version .cursor/rules with globs and alwaysApply core laws
– ✅ Mirror boundaries in eslint/exports/CI
– ✅ Spell out exact pnpm/tsc/test commands as definition of done
– ✅ Scope AWS/Lambda rules to those packages only
– ✅ Review rule diffs like policy changes

❌ Don’ts
– ❌ Don’t paste secrets into rules or examples
– ❌ Don’t write novel-length rules nobody updates
– ❌ Don’t allow deep imports the linter forbids
– ❌ Don’t let agents invent second build pipelines
– ❌ Don’t skip running the same checks CI runs

Related reading

Last updated on September 10, 2026


Discover more from CheatCoders

Subscribe to get the latest posts sent to your email.

2 Comments

Leave a Reply