Template Placeholders
Tool operation arguments (args) and environment-variable values (env) support template placeholders that datamitsu resolves before executing the tool. These let you reference file paths, project directories, and cache locations dynamically.
Available Placeholders
| Placeholder | Resolves To | args | env | Typical Use Case |
|---|---|---|---|---|
{file} | Single file path (per-file scope) | ✅ | ❌ | "{file}" |
{files} | Separate arguments per file | ✅ | ❌ | "{files}" |
{root} | Git repository root | ✅ | ✅ | "{root}/.config" |
{cwd} | Per-project working directory | ✅ | ✅ | "{cwd}/src" |
{toolCache} | Per-project, per-tool cache directory | ✅ | ✅ | "{toolCache}/tsbuildinfo" |
{target} | The single directory a scanner scans | ✅ | ❌ | "{target}" |
{bin:<alias>} | Executable of the operation app's dependency | ✅ | ✅ | "{bin:validator} check {files}" |
{file}, {files} and {target} are argument concepts and are not
available in env values (an environment variable holds a single string, not an
argument list). The path placeholders {root}, {cwd} and {toolCache} work in
both args and env.
{bin:<alias>} names a direct dependsOn edge of the operation's app
(by alias — { app: "jsonschema", as: "validator" } binds as
{bin:validator}) and resolves to the dependency's exact executable:
binary, uv and go targets today. It is validated at load time against the
app's edges, and expansion is read-only: no install, no prepare step.
:::warning Unknown placeholders are a hard error
datamitsu validates every {placeholder} in tool args and env at config-load
time. If you use a token that datamitsu does not substitute (a typo like
{toolcache}, an unsupported name like {cache}, or an args-only placeholder
such as {file} inside env), config loading fails immediately with a clear
message naming the tool, operation, and offending token — datamitsu never passes an
unsubstituted placeholder through to the tool. Shell brace groups ({js,ts}) and
Go templates ({{.Path}}) are not treated as placeholders and are left untouched.
:::
{file}
Expands to the path of a single file. Used in tools with scope: "per-file".
As entire argument — expands to a single argument:
args: ["{file}"];
// File: "src/main.js"
// Result: ["src/main.js"]
Embedded in a string — replaced inline:
args: ["--input={file}"];
// File: "src/main.js"
// Result: ["--input=src/main.js"]
{files}
Expands to one argument per file. Using it is what makes an operation's arity many: one process for the whole list, chunked only when the command line would overflow.
As entire argument — expands to multiple separate arguments:
args: ["--check", "{files}"];
// Files: ["src/main.js", "src/util.js"]
// Result: ["--check", "src/main.js", "src/util.js"]
Embedded in a string — files are joined with spaces:
args: ["--files={files}"];
// Files: ["src/main.js", "src/util.js"]
// Result: ["--files=src/main.js src/util.js"]
{root}
Expands to the git repository root path. Always resolves to the same value regardless of which project is being processed.
args: ["--config", "{root}/.config/tool.yml"];
// Root: "/home/user/repo"
// Result: ["--config", "/home/user/repo/.config/tool.yml"]
Use {root} to reference shared configuration files or resources at the repository level.
{cwd}
Expands to the per-project working directory. For scope: "per-project" tools, this is the detected project root. Falls back to the git root when the project path is empty (e.g., repository-scope tools).
args: ["--project", "{cwd}/tsconfig.json"];
// Per-project (packages/frontend): "/home/user/repo/packages/frontend/tsconfig.json"
// Repository scope: "/home/user/repo/tsconfig.json"
Use {cwd} to reference project-specific files within a monorepo.
{toolCache}
Expands to an isolated, per-project, per-tool cache directory. The path is computed using an XXH3-128 hash of the git root to ensure uniqueness.
~/.cache/datamitsu/cache/projects/{xxh3_128(gitRoot)}/cache/{relativeProjectPath}/{toolName}/
args: ["--cache-dir", "{toolCache}"];
// Tool: "eslint", Project: "packages/frontend"
// Result: ["--cache-dir", "~/.cache/datamitsu/cache/projects/a1b2c3/cache/packages/frontend/eslint/"]
Each tool and each project gets its own cache directory, preventing conflicts in monorepos.
If the cache path computation fails, the literal string {toolCache} is preserved unchanged.
Usage in environment values
The path placeholders also expand in env values, which is the correct way to
point a tool at its datamitsu-managed cache:
const toolsConfig = {
"golangci-lint": {
name: "golangci-lint",
operations: {
lint: {
app: "golangci-lint",
args: ["run", "--allow-parallel-runners"],
scope: "per-project",
env: {
// Resolves to an absolute, per-project cache path.
GOLANGCI_LINT_CACHE: "{toolCache}",
},
},
},
projectTypes: ["golang-package"],
},
};
Usage in Tool Configuration
Placeholders are used in the args field of tool operations:
const toolsConfig = {
prettier: {
name: "prettier",
operations: {
fix: {
app: "prettier",
args: ["--write", "--cache", "--cache-location", "{toolCache}", "{files}"],
globs: ["**/*.{js,ts,json,md}"],
scope: "per-project",
},
lint: {
app: "prettier",
args: ["--check", "{files}"],
globs: ["**/*.{js,ts,json,md}"],
scope: "per-project",
},
},
},
"golangci-lint": {
name: "golangci-lint",
operations: {
lint: {
app: "golangci-lint",
args: ["run", "--config", "{root}/.golangci.yml", "{cwd}/..."],
globs: ["**/*.go"],
scope: "repository",
},
},
},
};
{target}
Marks the one directory a tool walks itself, as opposed to a list of files you
hand it. Scanners want this: gitleaks dir, trivy fs, osv-scanner all take a
path and do their own traversal.
args: ["dir", "--redact", "--config", "{root}/.gitleaks.toml", "{target}"];
It exists because {root} is ambiguous. In --config {root}/.gitleaks.toml the
root is a path the tool reads; in dir {root} it is the thing it scans, and
nothing in the argument list distinguishes them. {target} says which one you
mean, and that is what lets datamitsu infer the operation's arity
exactly rather than guessing.
Handing a directory-scanning tool {files} instead is a silent mistake, not a
loud one. gitleaks dir accepts exactly one path and, given more, discards them
all and scans the working directory — same exit code, no warning, and your glob
and ignore rules never reached it.
{target} and {file}/{files} are mutually exclusive: a directory argument
and a file list are different shapes, and declaring both is a config error.
Expansion Order
Placeholders are resolved in this order:
{files}— expands to multiple arguments or inline{file}— expands to single argument or inline{root}— string replacement{cwd}— string replacement{toolCache}— computed and replaced
Multiple placeholders can appear in a single argument. All are resolved in the order above.
App environment placeholders
App env and runtimeEnv use a separate syntax from tool operation templates:
| Placeholder | App env | App runtimeEnv | prepare args/env | Resolves to |
|---|---|---|---|---|
${STORE} | Yes | Yes | No | Shared store directory |
${APP_DIR} | Yes | Yes | Yes | This app's install directory, when resolvable |
${APP_BIN:<alias>} | No | Yes | Yes | Exact executable of a direct dependency (binary, uv, go) |
${APP_PREPARED} | No | Yes, with prepare | Yes | The app's prepared-state directory |
runtimeEnv applies only to execution and never enters installer environments
or install hashes. The ${APP_BIN} target must be listed in the same app's
direct dependsOn — by name or by the edge's alias ({ app: "jsonschema", as: "validator" } binds as ${APP_BIN:validator}); binary, uv and go apps
resolve today, node/bun/jvm need a launcher that does not exist yet, and
shell apps are not installable targets. Missing or platform-unsupported
targets produce an error without fetching them. Malformed bindings and
unknown colon forms in runtimeEnv values are errors.
${APP_PREPARED} names the directory a declared prepare step fills
({store}/.prepared/<app>/<key>/out); subpaths are allowed. It is rejected
without a declared step and in app env (installers see env, and the state
does not exist at install time).
Inside a prepare step, every other placeholder is an error: the prepared
state is shared by every project, so project-bound forms (${STORE},
{root}, {cwd}, {file}, {files}, {target}, {toolCache}) cannot
enter it.
App env rejects ${APP_BIN and ${APP_PREPARED values; other config data
and environment keys remain literal for these purposes.