
Nullable New Params
- 314 installs
- 55.5k repo stars
- Updated August 4, 2026
- remotion-dev/remotion
Helps with ai & agent building tasks.
About
nullable-new-params is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted development.
- nullable-new-params
- AI & Agent Building
- AI-coding skill
Nullable New Params by the numbers
- 314 all-time installs (skills.sh)
- +60 installs in the week ending Aug 4, 2026 (Skillselion tracking)
- Ranked #2,249 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/remotion-dev/remotion --skill nullable-new-paramsAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 314 |
|---|---|
| repo stars | ★ 55.5k |
| Last updated | August 4, 2026 |
| Repository | remotion-dev/remotion ↗ |
What it does
Helps with ai & agent building tasks.
Files
Nullable new params
Use this skill when a change added a new parameter or type member as optional. In internal Remotion code, new inputs must be required and nullable so every caller makes an explicit choice.
Rule
- Internal contracts: write
name: T | null, notname?: T. - Call sites must pass
nullexplicitly when no value exists. - Implementation checks should prefer
value === null/value !== nullwhen null is the absence sentinel. - Do not use
undefinedas the absence sentinel for new internal APIs unless the surrounding local contract already standardizes onundefined. - The anti-pattern includes redundant shapes such as
frozenFrame?: number | null; make itfrozenFrame: number | null.
Public APIs are the exception. If the changed signature, props type, or options object is exported from a package public entrypoint or documented in packages/docs/docs, making the new field/argument required is a breaking change. Keep it optional or add a backwards-compatible overload/options path, then document/default it as appropriate.
Workflow
1. Inspect the diff for newly added optional members or parameters:
bun .agents/skills/nullable-new-params/scripts/find-new-optional-params.tsUseful variants:
bun .agents/skills/nullable-new-params/scripts/find-new-optional-params.ts origin/main...HEAD
bun .agents/skills/nullable-new-params/scripts/find-new-optional-params.ts --cached2. For each candidate, classify whether it is public:
- Public: exported from a package entrypoint, included in package
exports, or documented inpackages/docs/docs. - Internal: local helpers, internal component props, cross-file monorepo helpers, test utilities, internal context data, and types not exposed through package entrypoints.
- If unsure, grep package entrypoints and docs before changing API shape.
3. For internal candidates, refactor the type from optional to required nullable:
type Before = {
readonly frame?: number;
};
type After = {
readonly frame: number | null;
};For function parameters:
const before = (frame?: number) => {};
const after = (frame: number | null) => {};4. Update every caller/object literal to pass the value explicitly:
- Use
field: nullwhen absent. - Preserve existing values with
field: maybeValue ?? nullonly whenundefinedcan still enter from surrounding code. - Avoid hiding the required choice behind defaults in destructuring.
5. Update implementation logic:
- Replace truthy checks when
0,'', orfalseare valid values. - Prefer
value !== nullovervaluefor nullable numbers/strings/booleans. - Keep tests and fixtures explicit; do not make large fixtures
Partial<T>only to dodge the new field.
6. For public candidates, preserve backwards compatibility:
- Keep the new field optional in the public type.
- Resolve a concrete internal value at the boundary, usually with
const internal = publicValue ?? null. - Keep internal downstream types required nullable.
7. Verify:
- Run the scanner again until only intentional public API exceptions remain.
- Run focused tests or package builds for touched packages, for example
bunx turbo run make --filter='<package-name>'. - If docs changed, follow the
writing-docsskill.
Review checklist
- No new internal
?:member orparam?:parameter remains. - Every internal caller passes either a real value or
null. - Public APIs remain backwards-compatible.
- Nullable checks do not treat valid falsy values as absent.
- Tests cover at least one explicit
nullpath when behavior depends on absence.
interface:
display_name: 'Nullable New Params'
short_description: 'Fix new internal optional parameters'
default_prompt: 'Use $nullable-new-params to convert new internal optional parameters in my diff to required nullable parameters.'
#!/usr/bin/env bun
type Candidate = {
readonly file: string;
readonly line: number;
readonly code: string;
readonly reason: string;
};
const decoder = new TextDecoder();
const userArgs = Bun.argv.slice(2);
const gitArgs = ['diff', '--unified=0'];
if (userArgs.length === 0) {
gitArgs.push('HEAD');
} else {
gitArgs.push(...userArgs);
}
if (!userArgs.includes('--')) {
gitArgs.push('--', '*.ts', '*.tsx', '*.mts', '*.cts');
}
const diff = Bun.spawnSync(['git', ...gitArgs], {
stderr: 'pipe',
stdout: 'pipe',
});
if (diff.exitCode !== 0) {
const stderr = decoder.decode(diff.stderr).trim();
console.error(stderr || 'git diff failed');
process.exit(diff.exitCode);
}
const optionalMemberPattern =
/(^|[\s{(,;])(?:readonly\s+)?(?:[A-Za-z_$][\w$]*|["'][^"']+["'])\s*\?:/;
const optionalMethodPattern =
/(^|[\s{(,;])(?:readonly\s+)?[A-Za-z_$][\w$]*\s*\?\s*(?:<[^>]+>)?\s*\(/;
const isTypeScriptFile = (file: string): boolean => /\.(c|m)?tsx?$/.test(file);
const getReason = (code: string): string | null => {
const trimmed = code.trim();
if (
trimmed.length === 0 ||
trimmed.startsWith('//') ||
trimmed.startsWith('*') ||
trimmed.startsWith('/*')
) {
return null;
}
if (optionalMemberPattern.test(code)) {
return 'optional member or parameter (`?:`)';
}
if (optionalMethodPattern.test(code)) {
return 'optional method or function member (`?(`)';
}
return null;
};
const candidates: Candidate[] = [];
let currentFile: string | null = null;
let currentLine = 0;
for (const rawLine of decoder.decode(diff.stdout).split('\n')) {
if (rawLine.startsWith('+++ ')) {
const file = rawLine.slice(4).trim();
currentFile = file.startsWith('b/') ? file.slice(2) : null;
continue;
}
const hunk = /^@@ -\d+(?:,\d+)? \+(\d+)(?:,\d+)? @@/.exec(rawLine);
if (hunk) {
currentLine = Number(hunk[1]);
continue;
}
if (!currentFile || !isTypeScriptFile(currentFile)) {
continue;
}
if (rawLine.startsWith('+') && !rawLine.startsWith('+++')) {
const code = rawLine.slice(1);
const reason = getReason(code);
if (reason) {
candidates.push({
file: currentFile,
line: currentLine,
code: code.trim(),
reason,
});
}
currentLine++;
continue;
}
if (rawLine.startsWith(' ') || rawLine === '') {
currentLine++;
}
}
if (candidates.length === 0) {
console.log(
'No newly added optional members or parameters found in the TypeScript diff.',
);
process.exit(0);
}
console.log('New optional member/parameter candidates found:\n');
for (const candidate of candidates) {
console.log(`${candidate.file}:${candidate.line}`);
console.log(` ${candidate.reason}`);
console.log(` ${candidate.code}`);
}
console.log(
'\nFor internal APIs, change these to required nullable values (`name: T | null`) and pass `null` explicitly. Keep optional only for exported/documented public APIs where requiring the value would be breaking.',
);
process.exit(1);