$ npx rulesync-cli pull✓ Wrote CLAUDE.md (2 rulesets)# Coding Standards- Always use async/await- Prefer named exports
Rule Writing

CLAUDE.md for Deno Projects

Deno is secure by default and uses URL imports — AI generates Node patterns with require() and unrestricted permissions. Rules for permissions, JSR, Deno.serve, and std library.

7 min read·September 1, 2025

Deno is secure by default — AI uses --allow-all and require() that defeat it

Permission flags, JSR imports, Deno.serve, standard library, and Deno Deploy

Why Deno Needs Permission-First Rules

Deno is secure by default — no file system access, no network access, no environment variables unless explicitly granted with permission flags (--allow-read, --allow-net, --allow-env). AI assistants trained on Node.js generate code that assumes unrestricted access: reading files without --allow-read, making HTTP requests without --allow-net, and accessing process.env without --allow-env. The code runs in development (with --allow-all) and fails in production (with restricted permissions).

Deno also uses a different module system: URL imports and JSR (the JavaScript Registry) instead of node_modules and npm. AI generates require() calls, package.json dependencies, and node_modules imports — none of which are Deno-native. While Deno supports npm: specifiers for Node compatibility, native Deno code uses JSR and URL imports.

These rules target Deno 2.0+ which includes improved Node compatibility. They enforce Deno-native patterns while acknowledging npm interop when needed.

Rule 1: Explicit Permissions — Never --allow-all in Production

The rule: 'Grant only the permissions your app needs: deno run --allow-net=api.example.com --allow-read=./data --allow-env=DATABASE_URL main.ts. Never use --allow-all in production — it defeats Deno's security model. Scope permissions: --allow-net=hostname restricts to specific hosts, --allow-read=./path restricts to specific directories, --allow-env=KEY restricts to specific variables.'

For development: '--allow-all is acceptable during development for convenience. But define production permissions in deno.json tasks: { "tasks": { "start": "deno run --allow-net=0.0.0.0:8000,db.example.com --allow-read=./static --allow-env main.ts" } }. This documents exactly what the app needs and serves as the deployment command.'

AI generates deno run main.ts (no permissions — crashes on first I/O) or deno run --allow-all main.ts (no security). The correct approach: minimum permissions scoped to specific hosts, paths, and variables. This is Deno's killer feature — don't disable it.

  • --allow-net=host for network — scoped to specific hosts
  • --allow-read=./path for filesystem — scoped to specific directories
  • --allow-env=KEY for env vars — scoped to specific variables
  • --allow-all for development only — never in production
  • Document permissions in deno.json tasks — they're the security policy
⚠️ Permissions = Security Policy

--allow-all disables Deno's entire security model. Scoped permissions (--allow-net=api.example.com) are your security policy — they document and enforce exactly what your app can access. Don't trade Deno's killer feature for convenience.

Rule 2: JSR, URL Imports, and npm Interop

The rule: 'Use JSR (@std, @oak, @hono) as the primary package source: import { serve } from "jsr:@std/http". Use URL imports for modules not on JSR: import { z } from "https://deno.land/x/zod/mod.ts". Use npm: specifiers for Node packages when no Deno-native alternative exists: import express from "npm:express". Prefer JSR > URL imports > npm: specifiers — in that order.'

For the import map: 'Define import aliases in deno.json: { "imports": { "@/": "./src/", "oak": "jsr:@oak/oak@^17" } }. Use aliases in code: import { Application } from "oak". This is Deno's equivalent of TypeScript paths — cleaner imports without node_modules.'

For dependencies: 'Pin versions in deno.json imports or use deno.lock for lockfile. Run deno cache to pre-download dependencies. Deno caches modules globally — no node_modules directory, no install step. Run deno outdated to check for updates.'

Rule 3: Deno.serve for HTTP Servers

The rule: 'Use Deno.serve for HTTP servers — it's the built-in, high-performance server: Deno.serve({ port: 8000 }, (request: Request) => new Response("Hello")); Deno.serve uses Web Standard Request/Response — no framework needed for simple APIs. For larger apps, use Oak (@oak/oak) or Hono (jsr:@hono/hono) — both designed for Deno.'

For Web Standard patterns: 'Deno.serve handlers are standard fetch handlers — the same pattern as Cloudflare Workers, Service Workers, and Bun.serve. Skills transfer directly. Use URL for routing: const url = new URL(request.url); if (url.pathname === "/api/users") { ... }. Use request.json() for body, request.headers for headers.'

AI generates http.createServer (Node) or express() in Deno. Deno.serve is simpler, faster, and Deno-native. For routing beyond a few endpoints, use Oak or Hono — not Express (it works via npm: specifier but isn't optimized for Deno).

  • Deno.serve for HTTP — Web Standard Request/Response — no framework for simple APIs
  • Oak or Hono for larger apps — designed for Deno, not Node ports
  • URL for routing — request.json() for body — request.headers for headers
  • Same pattern as Workers/Bun — skills transfer across runtimes
  • Never http.createServer or express() — use Deno-native server
💡 Zero Install Testing

Deno.test is built into the runtime. deno fmt, deno lint, deno check — all built in. No npm install jest, no npx prettier, no tsc. The entire development toolchain ships with the runtime. Zero dependencies for tooling.

Rule 4: Standard Library (@std)

The rule: 'Use Deno's standard library (@std) for common utilities — it's audited, maintained, and designed for Deno. @std/fs for filesystem operations, @std/path for path manipulation, @std/testing for test utilities, @std/dotenv for .env loading, @std/crypto for hashing, @std/encoding for base64/hex. Import from JSR: import { ensureDir } from "jsr:@std/fs".'

For testing: 'Use Deno's built-in test runner: Deno.test("name", async () => { ... }). Use @std/testing assertions: assertEquals, assertThrows, assertRejects. Use @std/testing/bdd for describe/it syntax. Run tests: deno test. Coverage: deno test --coverage. No test framework installation needed — it's built into the runtime.'

For formatting and linting: 'deno fmt for formatting (built-in, opinionated, zero config). deno lint for linting (built-in, fast, recommended rules by default). deno check for type checking (TypeScript compiler built into Deno). Configure in deno.json: { "fmt": { "lineWidth": 100 }, "lint": { "rules": { "exclude": ["no-unused-vars"] } } }.'

  • @std/fs, @std/path, @std/testing, @std/crypto, @std/encoding — audited, maintained
  • Deno.test for tests — assertEquals, assertThrows from @std/testing
  • deno test for running — deno test --coverage for coverage
  • deno fmt for formatting — deno lint for linting — deno check for types
  • All built into the runtime — no separate tool installation

Rule 5: Deno Deploy and Production Patterns

The rule: 'Use Deno Deploy for serverless deployment: push to GitHub, Deno Deploy builds and deploys automatically. Deno Deploy runs V8 isolates (like Cloudflare Workers) at 35+ edge locations. Use Deno KV for key-value storage: const kv = await Deno.openKv(); await kv.set(["users", id], user); const result = await kv.get(["users", id]).'

For Deno KV: 'Deno KV is built into the runtime — no external service needed. Works locally (backed by SQLite) and on Deno Deploy (backed by FoundationDB). Atomic operations: await kv.atomic().check({ key, versionstamp }).set(["key"], value).commit(). Use for: sessions, cache, counters, feature flags.'

For production config: 'Use deno.json for all configuration: compilerOptions (TypeScript), imports (dependencies), tasks (scripts), fmt (formatting), lint (linting). Set permissions in deployment configuration — Deno Deploy respects the permissions you specify. Use environment variables via Deno.env.get("KEY") with --allow-env in permissions.'

ℹ️ Deno KV = Built-In Database

Deno KV is a key-value database built into the runtime — works locally (SQLite) and on Deno Deploy (FoundationDB). Atomic operations, no external service, no connection management. Import nothing — just await Deno.openKv().

Complete Deno Rules Template

Consolidated rules for Deno projects.

  • Explicit permissions: --allow-net=host, --allow-read=path, --allow-env=KEY — never --allow-all in prod
  • JSR > URL imports > npm: specifiers — import map in deno.json for aliases
  • Deno.serve for HTTP — Web Standard Request/Response — Oak/Hono for larger apps
  • @std library for utilities: fs, path, testing, crypto, encoding — audited and maintained
  • Deno.test built-in — deno fmt/lint/check built-in — zero tool installation
  • Deno KV for key-value — atomic operations — works locally and on Deploy
  • Deno Deploy for serverless edge — GitHub push deploys — V8 isolates
  • deno.json for all config — permissions documented in tasks — deno.lock for reproducibility