
Security News
PHP Foundation Says Chainguard Broke Security Embargo Ahead of Emergency Patches
The PHP Foundation says Chainguard broke a security embargo ahead of critical RCE patches, as leaks disrupted the coordinated release.
make-derive
Advanced tools
Hyper-configurable make where the compiler is an LLM.
A content-addressed, declarative, LLM-powered build system for derived files.
derive # run tasks with changes
derive readme # run specific task
derive --status # see what's stale
derive --force # ignore hashes, run anyway
.derive.lockNo more regenerating docs when nothing changed. No more forgetting to update generated files when sources change.
# Install
npx make-derive --init
# Edit derive.jsonc to define your tasks
# Then run
npx make-derive
Or install globally:
npm i -g make-derive
derive
npm i -g make-derive # npm
bun add -g make-derive # bun
pnpm add -g make-derive # pnpm
npx make-derive # npm
bunx make-derive # bun
pnpx make-derive # pnpm
git clone https://github.com/CyrusNuevoDia/derive
cd derive
bun install
bun run build:bin # Creates bin/derive
derive Run all tasks with changes
derive <task> Run specific task if changed
derive --force [task] Run regardless of hash state
derive --dry-run [task] Show what would run
derive --status Show per-task change status
derive --init Create starter derive.jsonc
derive --config <path> Use specific config file
derive --help Print help
derive --version Print version
| Flag | Short | Description |
|---|---|---|
--help | -h | Print help message and exit |
--version | -v | Print version number and exit |
--force | -f | Run task(s) regardless of hash state |
--dry-run | -n | Show what would run without executing |
--status | -s | Show per-task change status |
--init | Create a starter derive.jsonc in the current directory | |
--config <path> | -c | Use a specific config file instead of auto-discovery |
# Run all tasks that have changes
derive
# Run only the "readme" task (if changed)
derive readme
# Force run the "readme" task even if nothing changed
derive --force readme
derive -f readme
# See what would run without actually running
derive --dry-run
derive -n
# Check which tasks have pending changes
derive --status
derive -s
# Use a specific config file
derive --config ./config/derive.jsonc
derive -c ./config/derive.jsonc
# Initialize a new project
derive --init
Derive looks for config files in this order:
derive.ts (TypeScript, for dynamic configs)derive.jsonc (JSON with comments)derive.json (plain JSON)derive.toml (TOML)| Field | Type | Required | Description |
|---|---|---|---|
runner | string | Yes | Default command to execute. Must contain {prompt} placeholder. |
tasks | object | Yes | Map of task names to task configurations |
| Field | Type | Required | Description |
|---|---|---|---|
prompt | string | Yes | The prompt/instruction for the LLM |
sources | string[] | Yes | Glob patterns for source files to watch |
exclude | string[] | No | Glob patterns for files to exclude |
runner | string | No | Override the default runner for this task |
{
// Default runner command - {prompt} will be replaced with the assembled prompt
"runner": "claude --allowed-tools Read,Write,Edit --print {prompt}",
"tasks": {
"readme": {
"prompt": "Update README.md based on the current codebase. Keep it concise.",
"sources": ["src/**/*.ts"],
"exclude": ["src/**/*.test.ts", "src/**/*.spec.ts"]
},
"api-docs": {
"prompt": "Generate API documentation for the public exports.",
"sources": ["src/index.ts", "src/types.ts"],
// Override runner for this specific task
"runner": "llm -m gpt-4o {prompt}"
},
"changelog": {
"prompt": "Update CHANGELOG.md with recent changes.",
"sources": ["src/**/*.ts", "package.json"]
}
}
}
{
"runner": "claude --print {prompt}",
"tasks": {
"readme": {
"prompt": "Update README.md based on the codebase.",
"sources": ["src/**/*.ts"],
"exclude": ["src/**/*.test.ts"]
}
}
}
runner = "claude --print {prompt}"
[tasks.readme]
prompt = "Update README.md based on the codebase."
sources = ["src/**/*.ts"]
exclude = ["src/**/*.test.ts"]
[tasks.api-docs]
prompt = "Generate API documentation."
sources = ["src/index.ts", "src/types.ts"]
runner = "llm -m gpt-4o {prompt}"
Use derive.ts for dynamic configuration:
// derive.ts
import type { DeriveConfig } from "make-derive";
export default async (): Promise<DeriveConfig> => {
const pkg = await Bun.file("package.json").json();
return {
runner: `claude --allowed-tools Read,Write,Edit --print {prompt}`,
tasks: {
readme: {
prompt: `Update README.md for ${pkg.name}@${pkg.version}.
Include installation instructions and API reference.`,
sources: ["src/**/*.ts"],
exclude: ["src/**/*.test.ts"],
},
changelog: {
prompt: `Update CHANGELOG.md. Current version is ${pkg.version}.`,
sources: ["src/**/*.ts", "package.json"],
},
},
};
};
You can also export a static object:
// derive.ts
export default {
runner: "claude --print {prompt}",
tasks: {
readme: {
prompt: "Update the README.",
sources: ["src/**/*.ts"],
},
},
};
Derive uses SHA-256 hashes to track file changes:
When a task runs, derive assembles a prompt in XML format:
<prompt>Your task prompt here</prompt>
<changed-files>src/foo.ts, src/bar.ts</changed-files>
This tells your LLM what to do and which files changed, so it can focus on relevant updates.
Runners are executed in a login shell (-l -i -c) to ensure your PATH and environment are properly loaded from your shell config (.zshrc, .bashrc, etc.).
The {prompt} placeholder is safely shell-escaped before substitution.
.derive.lock tracks the state of your last run:
{
"version": 1,
"tasks": {
"readme": {
"last_run": "2024-01-15T10:30:00.000Z",
"sources_hash": "sha256:abc123...",
"files": {
"src/index.ts": "sha256:def456...",
"src/config.ts": "sha256:789abc..."
}
}
}
}
Commit this file. It enables:
Derive is runner-agnostic. Use any CLI that accepts a prompt:
{
// Claude Code
"runner": "claude --allowed-tools Read,Write,Edit --print {prompt}",
// OpenAI via llm CLI
"runner": "llm -m gpt-4o {prompt}",
// Anthropic via llm CLI
"runner": "llm -m claude-3-opus {prompt}",
// Local models via Ollama
"runner": "ollama run llama3 {prompt}",
// Custom script
"runner": "./scripts/generate.sh {prompt}"
}
Override the default runner per task:
{
"runner": "claude --print {prompt}",
"tasks": {
"readme": {
"prompt": "Update README.md",
"sources": ["src/**/*.ts"]
// Uses default runner: claude
},
"quick-task": {
"prompt": "Fix typos in comments",
"sources": ["src/**/*.ts"],
"runner": "llm -m gpt-4o-mini {prompt}" // Faster, cheaper model
}
}
}
Source patterns follow standard glob syntax:
| Pattern | Matches |
|---|---|
src/**/*.ts | All .ts files in src/ recursively |
*.md | All .md files in root |
src/*.ts | .ts files directly in src/ (not nested) |
{src,lib}/**/*.ts | .ts files in both src/ and lib/ |
Exclude patterns remove files from the matched set:
{
"sources": ["src/**/*.ts"],
"exclude": [
"src/**/*.test.ts",
"src/**/*.spec.ts",
"src/**/__tests__/**"
]
}
| Code | Meaning |
|---|---|
| 0 | Success (all tasks completed or skipped) |
| 1 | Task execution failed or invalid arguments |
| 2 | No config file found |
GPLv3
FAQs
Content-addressed LLM generation CLI — make, but the compiler is an LLM
We found that make-derive demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.

Security News
The PHP Foundation says Chainguard broke a security embargo ahead of critical RCE patches, as leaks disrupted the coordinated release.

Security News
PHP releases emergency patches for two critical RCE vulnerabilities as agencies urge organizations to take unpatched sites offline.

Threat Research
/Security News
A supply chain attack compromised LiteLLM 1.107.0 on PyPI and Docker, spreading malware that steals cloud credentials, developer secrets, and AI provider keys.