Configuration API
datamitsu uses JavaScript configuration files powered by the goja JavaScript runtime. Configuration is defined in datamitsu.config.js, datamitsu.config.mjs, or datamitsu.config.ts at your git repository root.
Required Exports
Every config file must export:
getMinVersion()— Returns a semver string specifying the minimum datamitsu version required. Checked beforegetConfig()is called. Configs without this function fail to load.getConfig(config)— Receives the previous layer's config and returns a newConfigobject.getRemoteConfigs()(optional) — Returns remote parent configs to load before this config.getBeforeConfigs()(optional) — Returns local config files to load as under-layers, the config-declared equivalent of the--before-configflag. Honoured only in the auto-discovered git-root config, and not evaluated when a--before-configflag is passed (the flag wins).
function getMinVersion(): string;
function getConfig(config: Config): Config;
function getRemoteConfigs(): Array<{ url: string; hash: string }>;
function getBeforeConfigs(): Array<{ path: string }>;
The special version "dev" (used when running from source) is treated as v0.0.0.
Config Structure
The getConfig() function returns a Config object:
interface Config {
apps?: BinManager.MapOfApps;
runtimes?: BinManager.MapOfRuntimes;
bundles?: Record<string, Bundle>;
tools?: MapOfTools;
parsers?: Record<string, Parser>;
lsp?: Record<string, LspProxy | LspDerived>;
projectTypes?: MapOfProjectTypes;
setup?: MapOfConfigSetup;
initCommands?: MapOfInitCommands;
ignoreRules?: string[];
sharedStorage?: Record<string, string>;
oci?: OCIRef;
}
Config Loading Order
Configuration is loaded in layers, each receiving the previous result as input:
default (embedded config.js)
↓ [getRemoteConfigs() resolved depth-first, if exported]
--before-config flags (for wrappers/libraries)
↓ [getRemoteConfigs() resolved depth-first]
declared before-configs (auto config's getBeforeConfigs(), only when no --before-config flag)
↓ [getRemoteConfigs() resolved depth-first]
auto (datamitsu.config.{js,mjs,ts} at git root)
↓ [getRemoteConfigs() resolved depth-first]
--config flags (for CI/testing overrides)
↓
final Config
- Each source must export
getMinVersion()— version is checked beforegetConfig()runs (fail-fast) - Each source can export
getRemoteConfigs()returningArray<{url: string, hash: string}>for recursive parent resolution - The auto git-root config can export
getBeforeConfigs()returningArray<{path: string}>to load local files as under-layers (parity with--before-config). It is honoured only at the git-root layer and skipped entirely when a--before-configflag is present; relative paths resolve against the git-root config's directory, no hash is required (local files are not downloads) ignoreRulesuse append semantics across config layers- Circular remote config dependencies are detected and produce an error
Apps (apps)
Apps define the tools datamitsu manages. The app kind is determined by which sub-object is present (binary, uv, node, jvm, go, or shell).
App Kinds
| Kind | Sub-object | Description |
|---|---|---|
binary | binary | Self-managed binaries downloaded from URLs |
uv | uv | Python packages installed via managed UV runtime |
node | node | npm packages installed via managed Node.js + pnpm |
jvm | jvm | Java applications executed via managed JDK |
go | go | Go tools built from source via managed Go SDK |
shell | shell | Shell commands with custom environment |
Common App Fields
All app kinds share these optional fields:
interface AppCommon {
required?: boolean; // Whether the app is required for init
files?: Record<string, string>; // filename → static content
links?: Record<string, string>; // linkName → relativePath in install dir
archives?: Record<string, ArchiveSpec>; // name → archive specification
env?: Record<string, string>; // Custom environment variables (all app kinds)
versionCheck?: {
disabled?: boolean; // Skip version check in verify-all
args?: string[]; // Override default ["--version"] args
};
}
Custom environment variables (env)
The optional env field applies to every app kind (binary, uv, node, jvm,
go, shell). It is injected both at install time (for the uv/node/go
dependency-install phase) and at run time (every app type).
Values support placeholder expansion, performed in Go and never written into the committed config:
${STORE}→ the shared datamitsu store path (cleaned bydatamitsu store clear).${APP_DIR}→ this app's install directory (per-app, config-hashed).
Precedence: any key already set by datamitsu or the runtime wins. A user
config can never relocate the pnpm store, uv cache, GOPATH, etc.
const apps = {
slidev: {
node: {
packageName: "@slidev/cli",
// ...
},
// Redirect playwright to download browsers into the datamitsu store
env: { PLAYWRIGHT_BROWSERS_PATH: "${STORE}/.playwright/browsers" },
},
};
Migration (alpha breaking change):
envwas previously a field onAppConfigShell(shell.env). It has moved to the top-levelenvfield shared by all app kinds. Move anyshell: { env: {...} }up one level toenv: {...}on the app.
Binary Apps
Binary apps download platform-specific executables with hash verification.
const apps = {
"golangci-lint": {
binary: {
version: "2.1.0",
binaries: {
linux: {
amd64: {
glibc: {
url: "https://github.com/golangci/golangci-lint/releases/download/v2.1.0/golangci-lint-2.1.0-linux-amd64.tar.gz",
hash: "abc123...", // SHA-256 (mandatory)
contentType: "tar.gz",
binaryPath: "golangci-lint-2.1.0-linux-amd64/golangci-lint",
},
},
},
darwin: {
arm64: {
unknown: {
url: "https://github.com/golangci/golangci-lint/releases/download/v2.1.0/golangci-lint-2.1.0-darwin-arm64.tar.gz",
hash: "def456...",
contentType: "tar.gz",
binaryPath: "golangci-lint-2.1.0-darwin-arm64/golangci-lint",
},
},
},
},
},
},
};
Binary-specific fields:
interface AppConfigBinary {
version?: string;
binaries: Partial<
Record<OsType, Partial<Record<ArchType, Partial<Record<LibcType, BinaryOsArchInfo>>>>>
>;
}
interface BinaryOsArchInfo {
url: string;
hash: string; // SHA-256 hash (mandatory)
contentType: BinContentType;
binaryPath?: string; // Path to binary within archive
extractDir?: boolean; // Extract entire archive to directory
}
The binaries map uses a three-level nested structure: os → arch → libc → BinaryOsArchInfo. Linux platforms use "glibc" or "musl" as the libc key; non-Linux platforms use "unknown".
Supported platforms: darwin/amd64/unknown, darwin/arm64/unknown, linux/amd64/glibc, linux/amd64/musl, linux/arm64/glibc, linux/arm64/musl, freebsd/amd64/unknown, openbsd/amd64/unknown, windows/amd64/unknown, windows/arm64/unknown
Supported archive types: tar.gz, tar.xz, tar.bz2, tar.zst, tar, zip, gz, bz2, xz, zst, binary
UV Apps (Python)
UV apps install Python packages in isolated environments using the managed UV runtime.
const apps = {
yamllint: {
uv: {
packageName: "yamllint",
version: "1.35.1",
requiresPython: ">=3.12", // optional, defaults to ">=3.12"
lockFile: "br:...", // brotli-compressed lock file (required)
runtime: "uv-default", // optional runtime override
},
},
};
UV-specific fields:
interface AppConfigUV {
packageName: string;
version: string;
requiresPython?: string; // Defaults to ">=3.12"
lockFile: string; // Brotli-compressed with "br:" prefix (required)
runtime?: string; // Runtime name override
}
Node Apps (Node.js/npm)
Node apps install npm packages using the managed Node.js runtime and pnpm.
const apps = {
eslint: {
node: {
packageName: "eslint",
version: "9.27.0",
binPath: "node_modules/.bin/eslint",
dependencies: {
"typescript-eslint": "^8.32.0",
"@eslint/js": "^9.27.0",
},
lockFile: "br:...", // brotli-compressed lock file (required)
runtime: "node-default", // optional runtime override
},
links: {
"eslint-config": "dist/eslint.config.js",
},
},
};
Node-specific fields:
interface AppConfigNode {
binPath: string; // Path to binary (e.g., "node_modules/.bin/eslint")
packageName: string;
version: string;
lockFile: string; // Brotli-compressed with "br:" prefix (required)
dependencies?: Record<string, string>;
runtime?: string; // Runtime name override
}
JVM Apps (Java)
JVM apps download JAR files and execute them via a managed JDK.
const apps = {
"openapi-generator-cli": {
jvm: {
version: "7.12.0",
jarUrl:
"https://repo1.maven.org/maven2/org/openapitools/openapi-generator-cli/7.12.0/openapi-generator-cli-7.12.0.jar",
jarHash: "abc123...", // SHA-256 (mandatory)
runtime: "jvm-default",
},
},
};
JVM-specific fields:
interface AppConfigJVM {
version: string;
jarUrl: string;
jarHash: string; // SHA-256 hash (mandatory)
mainClass?: string; // When set, uses java -cp instead of java -jar
runtime?: string; // Runtime name override
}
Go Apps
Go apps build a command-line tool from source using the managed Go SDK. The lock file carries both go.mod and go.sum so the build is reproducible and hash-verified.
const apps = {
govulncheck: {
go: {
packageName: "golang.org/x/vuln/cmd/govulncheck",
version: "v1.3.0",
lockFile: "br:...", // brotli-compressed lock file (required)
runtime: "go-default", // optional runtime override
},
},
};
Go-specific fields:
interface AppConfigGo {
packageName: string; // Go import path, e.g. "golang.org/x/vuln/cmd/govulncheck"
version: string; // Module version query, e.g. "v1.3.0"
lockFile: string; // Brotli-compressed JSON {"mod","sum"} with "br:" prefix (required)
runtime?: string; // Runtime name override
}
The tool is built with go build -trimpath -mod=readonly in an isolated, hardened environment. See the Supply Chain Security guide for the full defense model.
Shell Apps
Shell apps wrap system commands. Set environment variables through the shared
top-level env field.
const apps = {
"my-script": {
shell: {
name: "bash",
args: ["-c", "echo hello"],
},
env: { MY_VAR: "value" },
},
};
Shell-specific fields:
interface AppConfigShell {
name: string; // Command name
args?: string[]; // Default arguments
}
Bundles (bundles)
Bundles store static content (files, archives) in a hash-keyed cache directory and expose it through .datamitsu/ symlinks. Unlike apps, bundles are not executable.
interface Bundle {
version?: string;
files?: Record<string, string>;
archives?: Record<string, ArchiveSpec>;
links?: Record<string, string>;
}
| Field | Type | Description |
|---|---|---|
version | string | Version identifier (changes trigger cache invalidation) |
files | Record<string, string> | Filename to content mapping |
archives | Record<string, ArchiveSpec> | Named archives (inline or external) |
links | Record<string, string> | Link name to relative path in install dir |
A bundle must have at least files or archives. Link names must be unique across both apps and bundles.
Link values can point to files or directories within the install dir. Use "." to link to the entire bundle directory.
Example:
const bundles = {
"agent-skills": {
version: "1.0",
files: {
"agents.md": "# Agent instructions...",
"skills/search.md": "# Search skill...",
},
links: {
"agent-skills-dir": ".", // link to entire bundle directory
"agents-md": "agents.md", // link to a single file
},
},
};
Install path: {cache}/.bundles/{name}/{hash}/
See Managed Content (Bundles) for a full guide.
Runtimes (runtimes)
Runtimes define how language-specific package managers are provisioned.
interface RuntimeConfig {
kind: "node" | "uv" | "jvm" | "go";
mode: "managed" | "system";
managed?: RuntimeConfigManaged; // Required for managed mode
system?: RuntimeConfigSystem; // For system mode
node?: RuntimeConfigNode; // Required when kind is "node"
uv?: RuntimeConfigUV; // When kind is "uv"
jvm?: RuntimeConfigJVM; // Required when kind is "jvm"
go?: RuntimeConfigGo; // Required when kind is "go"
}
Managed Mode
In managed mode, datamitsu downloads the runtime binary itself:
const runtimes = {
"node-default": {
kind: "node",
mode: "managed",
managed: {
binaries: {
linux: {
amd64: {
glibc: {
url: "https://nodejs.org/dist/v26.2.0/node-v26.2.0-linux-x64.tar.xz",
hash: "abc123...", // SHA-256 (mandatory)
contentType: "tar.xz",
binaryPath: "node-v26.2.0-linux-x64/bin/node",
extractDir: true,
},
},
},
// ... other platforms
},
},
node: {
nodeVersion: "26.2.0",
pnpmVersion: "11.5.0",
pnpmHash: "def456...", // SHA-256 of pnpm package
},
},
};
System Mode
In system mode, the runtime uses the system-installed version:
const runtimes = {
"uv-system": {
kind: "uv",
mode: "system",
system: {
command: "uv",
systemVersion: "1.0", // Bump to invalidate cache
},
uv: {
pythonVersion: "3.12",
},
},
};
Runtime Kind Configuration
Node Runtime:
interface RuntimeConfigNode {
nodeVersion: string; // e.g., "26.2.0"
pnpmVersion: string; // e.g., "11.5.0"
pnpmHash: string; // SHA-256 of pnpm package (mandatory)
}
UV Runtime:
interface RuntimeConfigUV {
pythonVersion?: string; // e.g., "3.14.3"
}
JVM Runtime:
interface RuntimeConfigJVM {
javaVersion: string; // e.g., "25"
}
Go Runtime:
interface RuntimeConfigGo {
goVersion: string; // Go SDK version to build with, e.g., "1.26.3"
}
Tools (tools)
Tools define fix and lint operations that datamitsu executes.
interface Tool {
name: string;
operations: Partial<Record<"fix" | "lint", ToolOperation>>;
outputParser?: { module: string; parser: string }; // module = parsers entry; parser = dispatch key
projectTypes?: string[]; // Restrict to specific project types
skip?: boolean; // Report as skipped and never run (instead of omitting the tool)
skipReason?: string; // Human-readable reason shown in the skipped report
}
interface ToolOperation {
app: string; // App name from apps
args: string[]; // Supports template placeholders ({file}, {files}, {root}, {cwd}, {toolCache})
globs?: string[]; // File patterns (doublestar syntax; `!` negation not supported). Omit to match all discovered files.
excludeGlobs?: string[]; // Patterns removed from the matched set (doublestar syntax)
scope: "repository" | "per-project" | "per-file";
batch?: boolean; // Batch files into single execution (default: true)
priority?: number; // Execution order (lower = first, default: 0)
invalidateOn?: string[]; // Files that invalidate cache
env?: Record<string, string>; // Extra environment variables; values support {root}, {cwd}, {toolCache}
input?: "file" | "stdin"; // How file content reaches the tool (default: "file")
output?: "inplace" | "stdout"; // How the result is captured (default: "inplace")
}
Example:
const toolsConfig = {
prettier: {
name: "prettier",
operations: {
fix: {
app: "prettier",
args: ["--write", "{files}"],
globs: ["**/*.{js,ts,jsx,tsx,json,md,yaml,yml}"],
scope: "per-project",
},
lint: {
app: "prettier",
args: ["--check", "{files}"],
globs: ["**/*.{js,ts,jsx,tsx,json,md,yaml,yml}"],
scope: "per-project",
},
},
projectTypes: ["typescript", "javascript"],
},
};
Scope Types
| Scope | Description | Working Directory |
|---|---|---|
repository | Runs once for the entire repository | Git root |
per-project | Runs once per detected project | Project root |
per-file | Runs once per matched file | Project root |
See Template Placeholders for the {file}, {files}, {root}, {cwd}, and {toolCache} placeholders available in args (and {root}, {cwd}, {toolCache} in env). Unknown placeholders fail config loading rather than being passed through to the tool.
Skipping a tool (skip / skipReason)
Set skip: true to keep a tool in the config but report it as skipped rather
than running it. This is preferable to conditionally omitting the tool: an omitted
tool is invisible, whereas a skipped one is shown in the run summary (⊘ <tool> skipped (<reason>)), the footer's · N skipped count, and the --explain=json
skipped array. Use skipReason to explain why.
A tool is also skipped automatically when its binary has no build for the
current OS/architecture/libc — a soft skip that no longer fails the run. See
--fail-on-skip to harden CI against that case.
// BAD: conditionally omitting the tool — datamitsu never reports it was left out
const toolsConfig = {
...(facts().env.CI && { trufflehog: { name: "trufflehog", operations: { lint } } }),
};
// GOOD: keep the tool, mark it skipped with a reason
const toolsConfig = {
trufflehog: {
name: "trufflehog",
skip: !facts().env.CI,
skipReason: "runs in CI only",
operations: { lint: { app: "trufflehog", scope: "repository" } },
},
};
Output Parser (outputParser)
A tool may reference a WASM output parser to turn its
raw text output into structured results. The reference is an object with two
parts: module — the parsers entry to load (a specific WASM artifact, so
different versions are different entries) — and parser — the parser inside
that module to run (a name from datamitsu devtools parsers list). module must
name an existing parsers entry; a dangling reference is a config error.
const toolsConfig = {
hadolint: {
name: "hadolint",
outputParser: { module: "core", parser: "hadolint" },
operations: { lint: { app: "hadolint", args: ["{file}"], scope: "per-file" } },
},
eslint: {
name: "eslint",
outputParser: { module: "core", parser: "eslint" },
operations: {
lint: { app: "eslint", args: ["--format", "json", "{file}"], scope: "per-file" },
},
},
};
Keeping module and parser separate is what makes multiple module versions
work: declare parsers: { core: {…v2}, core_legacy: {…v1} } and point each tool's
outputParser.module at the version it needs.
Output parsers are a Phase-1 plumbing surface: the pipeline (declare → download
→ verify → load → invoke) ships now with a trivial echo parser, but real
diagnostic parsers (hadolint, yamllint, …) arrive in a later phase. See
WASM Output Parsers for the architecture.
Formatting input/output modes (input / output)
By default a tool receives file paths as arguments (input: "file") and either
mutates files in place or has its combined stdout+stderr captured for reporting
(output: "inplace"). A formatter that reads from standard input and writes
the formatted document to standard output uses the stdin → stdout → diff contract
instead:
| Field | Values | Default | Meaning |
|---|---|---|---|
input | "file" | "stdin" | "file" | "stdin" pipes the target file's content to the tool's standard input |
output | "inplace" | "stdout" | "inplace" | "stdout" captures stdout (kept apart from stderr) as the new content |
When output: "stdout", datamitsu treats the tool's stdout as the full new file
text, computes a minimal line-based diff against the original, and applies
only the changed lines — no change yields no edits. This keeps the formatter
sandboxed from the filesystem and produces precise edits (reusable as LSP
TextEdit ranges later). Use it with scope: "per-file".
// BAD: a stdin-only formatter forced through the in-place vehicle — it never
// receives the file and its formatted output is swallowed into the report.
const toolsConfig = {
shfmt: {
name: "shfmt",
operations: {
fix: { app: "shfmt", args: ["{file}"], scope: "per-file" },
},
},
};
// GOOD: feed content on stdin, capture stdout, let the core diff + apply.
const toolsConfig = {
shfmt: {
name: "shfmt",
operations: {
fix: {
app: "shfmt",
args: [], // tool reads stdin, writes stdout
scope: "per-file",
input: "stdin",
output: "stdout",
},
},
},
};
See Formatting pipeline for the end-to-end flow.
Project Types (projectTypes)
Project types define markers used to detect project boundaries in the repository.
const projectTypes = {
typescript: {
markers: ["package.json", "tsconfig.json"],
},
golang: {
markers: ["go.mod"],
},
python: {
markers: ["pyproject.toml", "setup.py"],
},
};
Config Setup (setup)
Config setup entries define configuration files that datamitsu setup generates.
interface ConfigSetup {
content?: (context: ConfigContext) => string;
deleteOnly?: boolean; // Only delete, don't create
expectChainHash?: string; // Pin the upstream-chain hash; abort setup on drift
linkTarget?: string; // Create symlink instead of writing content
otherFileNameList?: string[]; // Conflicting files to delete
projectTypes?: string[]; // Restrict to project types
scope?: "project" | "git-root"; // Where to create: "project" (default) or "git-root" (once at root)
tools?: string[]; // Tool name(s) this config belongs to; enables `setup --tools` scoping
}
The optional tools field associates a config file with one or more tools (by
name, matching keys in tools). datamitsu setup --tools <names>
then regenerates only the config files whose tools intersect the selected set —
every other config, including unassociated infrastructure files, is left
untouched. Omit tools for files not tied to a single tool (.gitignore,
lefthook.yaml); those are skipped whenever --tools is passed.
Pinning the upstream chain (expectChainHash)
Config files are generated by layering: a shared config (and any
getRemoteConfigs() / getBeforeConfigs() layers) runs first, and your root
config — the one at the git root, or an explicit --config — runs last on top.
When you override a generated config in your root layer, those overrides assume a
particular upstream baseline. If the shared config later changes that baseline, a
plain datamitsu setup would re-derive the file and could silently drop your
overrides.
expectChainHash guards against exactly that. Set it on a root-layer entry to
pin the XXH3-128 hash of the content entering your layer — the output of the
whole upstream chain, before your layer transforms it:
const setup = {
"eslint.config.mjs": {
// Pin the upstream baseline this override was written against.
expectChainHash: "xxh3:0a1b2c3d4e5f60718293a4b5c6d7e8f9",
content: (context) => {
/* your overrides on top of the shared config */
},
},
};
On every datamitsu setup (including --dry-run), the hash is recomputed and
compared. When it matches, setup proceeds normally. When the upstream chain has
drifted, setup aborts before writing anything and prints the new hash
plus the full incoming content, so you can review the change against your
overrides, update the pin, and re-run. Key properties:
- Opt-in, per file. Files without
expectChainHashbehave exactly as before. To attach a pin, the file must have an entry in your root config (an entry with onlyexpectChainHashand nocontentis valid). - Root layer only. A pin is honoured solely on the topmost (root) layer; pins on intermediate layers are ignored.
- Byte-for-byte. The incoming content is hashed verbatim with no
normalization — any change (upstream or a handler that folds in your on-disk
file) moves the hash. Format:
"xxh3:<32-hex>"(a bare 32-hex value is also accepted). - Bypass. Pass
datamitsu setup --no-verify-hashto skip the check and write regardless of drift.
To obtain the value the first time, declare the entry with a placeholder pin and
read the real hash with datamitsu config chain-hash <file>
(it prints exactly what the gate checks, no error needed):
pin=$(datamitsu config chain-hash eslint.config.mjs) # -> xxh3:…
The content function receives a context object:
interface ConfigContext {
cwdPath: string; // Current working directory
rootPath: string; // Git repository root
datamitsuDir: string; // Relative path from cwdPath to .datamitsu/
isRoot: boolean; // Is cwdPath the git root?
projectTypes: string[]; // Detected project types
existingContent?: string; // Previous layer's generated content (if any)
existingPath?: string; // Current file path (if exists)
originalContent?: string; // Unmodified content from disk
}
Example:
const init = {
"eslint.config.js": {
projectTypes: ["typescript", "javascript"],
tools: ["eslint"], // scope `setup --tools eslint` to this file
content: (context) => {
const configPath = tools.Path.forImport(
tools.Path.join(context.datamitsuDir, "eslint.config.js"),
);
return `import config from "${configPath}";\nexport default config;\n`;
},
otherFileNameList: [".eslintrc", ".eslintrc.json", ".eslintrc.yml"],
},
};
Init Commands (initCommands)
Init commands run shell commands during datamitsu init.
interface InitCommand {
command: string; // App name from apps
args: string[]; // Command arguments
projectTypes?: string[]; // Restrict to project types
}
Example:
const initCommands = {
lefthook: {
command: "lefthook",
args: ["install"],
},
};
Archives
Apps can bundle directory trees via inline or external archives.
interface ArchiveSpec {
inline?: string; // Brotli-compressed tar: "tar.br:..." prefix
url?: string; // External archive URL
format?: string; // Required for external: "tar", "tar.gz", etc.
hash?: string; // SHA-256 required for external archives
}
Extraction order: Archives extracted first (sorted alphabetically), then files written (files overwrite archive contents). Package manager runs after both.
Archives are supported on UV apps, node apps, and bundles.
Ignore Rules
The ignoreRules field accepts .datamitsuignore-syntax rules:
const config = {
ignoreRules: ["vendor/**/*: *", "**/*.generated.go: golangci-lint"],
};
See Ignore Rules for the full syntax reference.
Shared Storage (sharedStorage)
A map[string]string field that flows through the config chain as ordinary JS input. Use it to pass arbitrary data between config layers that doesn't fit the typed config structure.
/// <reference path=".datamitsu/datamitsu.config.d.ts" />
// Setting values in a config layer
function getConfig(input) {
return {
...input,
sharedStorage: {
...input.sharedStorage,
"llms-txt": "# Project documentation...",
"feature-flag": "true",
},
};
}
globalThis.getConfig = getConfig;
// Reading values in a downstream config layer
function getConfig(input) {
const llmsTxt = input.sharedStorage?.["llms-txt"];
// Use the value...
return { ...input };
}
globalThis.getConfig = getConfig;
See Managed Content - Shared Storage for usage examples.
OCI Bundle (oci)
Pins the OCI bundle that seeds the tool store: the registry repository plus the mandatory SHA-256 digest.
interface OCIRef {
ref: string; // full reference incl. registry host, e.g. "ghcr.io/owner/tool-store"
digest: string; // "sha256:" + 64 lowercase hex characters — mandatory
signer?: {
identity: string; // sigstore certificate identity (e.g. a workflow ref)
issuer: string; // OIDC issuer URL
};
}
function getConfig(input) {
return {
...input,
oci: {
ref: "ghcr.io/owner/tool-store",
digest: "sha256:6c3c624b58dbbcd3c0dd82b4c53f04194d1247c6eebdaab7c610cf7d66709b3b",
},
};
}
Rules:
refmust include the registry host as its first segment — there is no default host and no docker.io shorthand. Tags and digests insiderefare rejected; content is pinned exclusively bydigest.ocichains through the layers as a scalar: the last layer that set or spread it wins, and the effective config holds at most one declaration. A layer that rebuilds its output without{...input}silently drops the inherited value (a debug log flags this); reset explicitly withoci: undefinedoroci: null.signeroptionally pins the sigstore keyless identity of the bundle publisher. When set, pull must verify the signature before layout (not yet supported by the current builds — a setsignercurrently fails the seed rather than silently skipping the check).
See the OCI Bundles guide for the trust model and the seeding lifecycle.
Output Parsers (parsers)
Parsers are signed Rust→WASM modules that extract structured results from a
tool's raw text output. A parsers entry is a url+hash data artifact —
modeled on ArchiveSpec/Bundle, not on an app: there is no runtime and no
lockfile, because it is data, not a process. It is downloaded, SHA-256 verified,
content-addressed in the store, and loaded into a sandboxed WASM runtime
(wazero).
interface Parser {
url: string; // URL of the .wasm module
hash: string; // SHA-256 (64 lowercase hex) — mandatory
// No `version` field — see note below.
}
| Field | Type | Description |
|---|---|---|
url | string | URL of the .wasm module |
hash | string | SHA-256 hash, 64 lowercase hex — mandatory (see below) |
The entity is intentionally url + hash only. A module reports its own
build-injected version (and the tools it parses) through its WASM describe
export, surfaced by datamitsu devtools parsers list.
The module is the single source of truth, so there is no version field to drift
from reality.
A tool opts into a parser via tool.outputParser.
The same parser referenced by several tools is downloaded once.
function getConfig(input) {
return {
...input,
parsers: {
echo: {
url: "https://github.com/owner/repo/releases/download/v1.2.3/datamitsu_parsers_1.2.3.wasm",
hash: "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
version: "1.2.3",
},
},
};
}
The SHA-256 hash is mandatory per the security policy
— an empty or malformed hash is a config error, not a warning. The module ships
only as a versioned asset on the GitHub Release (datamitsu_parsers_<version>.wasm);
config maintainers take the url from that release asset and the hash from the
signed checksums.txt. See
WASM Output Parsers for the trust model.
// BAD: missing hash — refused at config load
parsers: {
echo: { url: "https://example.com/datamitsu_parsers.wasm" },
}
// GOOD: mandatory SHA-256 hash from the release's signed checksums.txt
parsers: {
echo: {
url: "https://example.com/datamitsu_parsers.wasm",
hash: "0123…cdef", // 64 lowercase hex
},
}
LSP Servers (lsp) — reserved
:::info Reserved surface
lsp is a declaration-only entity reserved for a later phase. The types load
and are structurally validated today, but there is no runtime behavior in
this release. Declaring lsp entries does nothing yet.
:::
Each entry is one of two discriminated shapes:
// Wrap a standalone language-server app, scoped by project types.
interface LspProxy {
type: "proxy";
app: string; // app name (in `apps`) providing the server
projectTypes: string[]; // must be non-empty
order?: number; // precedence; ties break alphabetically by entry name
}
// Reuse an existing tool's projectTypes/globs and its outputParser.
interface LspDerived {
type: "derived";
tool: string; // tool name (in `tools`) — must exist
order?: number; // precedence; ties break alphabetically by entry name
}
Structural validation today: type must be "proxy" or "derived"; a proxy
requires app plus a non-empty projectTypes; a derived requires a tool
that exists in tools. The optional order controls precedence, with ties
broken alphabetically by entry name (the same sort.Strings-by-name convention
the planner uses).
function getConfig(input) {
return {
...input,
lsp: {
"go-lsp": { type: "proxy", app: "gopls", projectTypes: ["golang"] },
hadolint: { type: "derived", tool: "hadolint" },
},
};
}
JavaScript APIs
The following APIs are available in configuration files.
Format Utilities
// YAML
YAML.parse(text);
YAML.stringify(value);
// TOML
TOML.parse(text);
TOML.stringify(value);
// INI
INI.parse(text);
INI.stringify(sections);
INI.toRecord(sections);
Path Utilities
// Join path segments
tools.Path.join("src", "components", "Button.tsx");
// Get absolute path
tools.Path.abs("relative/path");
// Get relative path (basePath defaults to git root)
tools.Path.rel(targetPath, basePath);
// Convert to ES module import path (ensures ./ or ../ prefix)
tools.Path.forImport(tools.Path.join(context.datamitsuDir, "eslint.config.js"));
// → "./.datamitsu/eslint.config.js"
Config Link Utilities
// Get relative path from a file to a managed config link
tools.Config.linkPath("eslint", "eslint-config", fromPath);
Ignore Utilities
// Parse .gitignore-style content
const groups = tools.Ignore.parse(content);
// Stringify back with optional group ordering
const output = tools.Ignore.stringify(groups, groupOrder);
Platform Information
const info = facts();
// info.os → "linux", "darwin", "windows", etc.
// info.arch → "amd64", "arm64"
// info.libc → "glibc", "musl", "unknown" (Linux-only detection)
// info.isInGitRepo → true/false
// info.isMonorepo → true/false
// info.env → environment variables
Security Requirements
All artifacts downloaded from the internet must have a SHA-256 hash specified:
- Binary apps:
hashfield on each platform entry - JVM apps:
jarHashfield - External archives:
hashfield - Node runtime (pnpm):
pnpmHashfield - Output parsers:
hashfield on eachparsersentry
Missing or empty hashes are treated as configuration errors.