TypeScript with 74% less syntax, bad patterns removed, and one way to do each thing — simpler to learn, less for AI to get wrong, easier to review.
TypeScript gives you three ways to declare variables, four ways to write functions, five ways to handle errors. More forms means harder reads, harder reviews, and more ways for LLMs to generate subtly wrong code.
throw,
try/catch, rejected Promise, error-first
callback, return tuple
→
PromiseResult<T>
?,
| undefined, | null
→
| null
function f() {}
type,
interface, class
→
type T = {}
var,
let, const
→
const
&&,
||, ??, ?.,
switch expression
→
if / else
class,
extends, implements,
abstract, private,
protected, new
→
banned
as,
any, !,
@ts-ignore, @ts-expect-error,
satisfies
→
banned
infer,
index signatures, variadic tuples
→
banned
ShotScript is not a language or a compiler. It's a set of tools that constrain TypeScript to one way of doing things.
Interface, class, try/catch, arrow functions,
ternaries, any — common TypeScript, with every
ShotScript rule applied.
// ❌ interface · optional fields interface BaseUser { id: number name: string } // ❌ intersection · optional fields type User = BaseUser & { avatar?: string role?: 'admin' | 'member' } // ❌ class · constructor · private · this class UserService { private cache: Map<number, User> = new Map() constructor(private readonly db: Database) {} // ❌ optional param · try/catch · throw · as async findUser(id?: number): Promise<User | undefined> { if (!id) return undefined try { const res = await fetch(`/users/${id}`) if (!res.ok) throw new Error(`HTTP ${res.status}`) return res.json() as User } catch (e) { return undefined } } } // ❌ arrow fn · ternary · optional chaining · any const label = (u?: User): string => u?.avatar ? `<img src="${u.avatar}">` : u?.name ?? 'Unknown' // ❌ let · any · anonymous callback let names: any[] = users.map(u => u.name)
import type { PromiseResult } from 'shotscript/std' import { safeFetch, jsonParse } from 'shotscript/std' type BaseUser = { readonly id: number readonly name: string } // nested property, not & — fields stay in one place type User = { readonly base: BaseUser readonly avatar: string | null readonly role: 'admin' | 'member' | null } type UserService = { readonly db: Database readonly cache: Map<number, User> } async function findUser( svc: UserService, id: number, ): PromiseResult<User> { const [res, err] = await safeFetch(`/users/${id}`) if (err !== null) return [null, err] return jsonParse<User>(await res.text()) } function label(user: User): string { if (user.avatar !== null) { return `<img src="${user.avatar}">` } return user.base.name } function toName(user: User): string { return user.base.name } const names: string[] = users.map(toName)
package.json.
Step 2a — New project (no package.json):
Ask me "What would you like to build?" Based on my
answer, pick the most appropriate minimal TypeScript starter
(Node + tsx for scripts, Hono/Express for APIs, Vite + React
for web apps, etc.), scaffold it, then continue.
Step 2b — Existing project: Continue.
Step 3 — Install ShotScript. Run
npm install --save-dev shotscript. Then add
{ "name": "shotscript/plugin" } to the
plugins array in
compilerOptions in tsconfig.json, set
extends in tsconfig.json to
"shotscript/tsconfig/shotscript.json", add
"shotscript/fmt" to extends in
biome.json (create it if missing), prepend
shotscript 'src/**/*.{ts,tsx}' to the
build script in package.json so lint runs at
build time, and run npx shotscript init to
write the ShotScript coding guide to
AGENTS.md (merged into any existing file) and
install the slash commands.
Step 4 — Offer migration. If existing
TypeScript source files are present, ask me whether to
migrate them with /shotscript-migrate. Skip
otherwise.
Step 5 — Confirm. Show me the final
tsconfig.json and biome.json so I can verify.
About /shotscript-migrate →
// package.json { "scripts": { "build": "shotscript 'src/**/*.{ts,tsx}' && tsc ..." } }
Copies the ShotScript coding rules into your project so every AI assistant codes in ShotScript style automatically. Merge into an existing AGENTS.md if you already have one.
$ npx shotscript init
Surfaces lint errors inline in VS Code via the TypeScript language server. Other editors (Zed, Neovim, etc.) may not fully support tsserver plugins even when using the TS language server — the build step above is the reliable enforcement for all environments.
// tsconfig.json { "compilerOptions": { "plugins": [{ "name": "shotscript/plugin" }] } }
// biome.json { "extends": ["shotscript/fmt"] }
// tsconfig.json { "extends": "shotscript/tsconfig/shotscript.json" }
import { safeFetch, jsonParse } from 'shotscript/std' import type { Result, PromiseResult } from 'shotscript/std'
Three Claude Code slash commands — fix violations in-place, migrate existing codebases end-to-end, and explain any rule on demand. One install command, no manual rewrites.
ShotScriptSkills →