# Resource and helper tooling
Status: done. Some agent libraries ship helper tooling -- shell or Python
scripts, or a compiled binary -- that their skills and agents invoke at runtime.
This document specifies how a source ships that tooling, how `mind` installs it
(including an optional build step for compiled tooling), and how an item
references it by a stable, namespace-safe path.
## Overview
A skill or agent often calls a helper script it ships next to itself
(`skills/voice/resources/mine-commits.sh`) or a shared helper used by several
items (`resources/detect-project.sh`). Copying the files is already handled --
`learn` copies a skill's whole directory -- so the unsolved parts are two others:
- **Reference.** The item must name the tooling's installed path. A hardcoded
`~/.claude/skills/voice/resources/...` breaks the moment a prefix renames the
item to `jk:voice`, and is ambiguous when more than one agent home is
configured (which home?). This is the same fragility `{{ns:name}}` already
fixes for sibling *names* (namespacing.md), one level down: a path, not a name.
- **Standalone and compiled tooling.** A helper shared across items is neither a
skill, an agent, nor a rule, so no kind installs and tracks it. And compiled
tooling must be built, with the output landing somewhere stable to reference.
`mind` answers these with: a `tool` item kind (a store-only installable that
exists to be referenced), path-reference tokens (`{{self}}`, `{{tools:name}}`,
`{{path:ref}}`, expanded at install like `{{ns:}}`), and an optional per-item
build hook (install-hooks.md, HOOK-70..73) for tooling that must be compiled.
Tokens expand to the store path (`~/.mind/store/...`), which is single and
home-independent, so a reference resolves the same regardless of how many agent
homes are configured or what prefix is in force.
The rest of this document states the rules normatively.
## The `tool` kind
- `TOOL-1` `tool` is a fourth item kind alongside skill, agent, and rule. By
convention, every immediate subdirectory of a `tools/` directory (under each
scan root) is a tool; the whole directory is the item. Unlike a skill
(`skills/<name>/SKILL.md`), a tool needs no anchor file: a bare `tools/<name>/`
directory is a tool, and its contents are the final tool state as installed (no
build is implied). An authoritative `mind.toml` overrides convention scanning
as for any kind (discovery.md).
- `TOOL-2` A tool's metadata comes from an optional `TOOL.md` in its directory:
frontmatter `description`, `bin`, and `build`. A `mind.toml` `[[items]]` entry
overrides those fields. With neither a `TOOL.md` nor a `mind.toml` entry, a tool
has no description and `bin`/`build` come only from convention (TOOL-5).
- `TOOL-3` A tool installs to the store (`~/.mind/store/tool/<effective_name>/`)
like any item, but by default is NOT linked into any agent home: it carries no
symlink and the Claude harness does not discover it. Its manifest entry records
the store path with an empty `links` set. `forget` removes the store copy;
`recall` reports it installed. A tool is reached only by reference (TOOL-10..12).
- `TOOL-4` A tool MAY declare an explicit `link` (`mind.toml` `[[items]].link`)
to also surface it under each agent home, for the rare tool that should be
discoverable in place; absent a `link`, a tool is store-only (TOOL-3).
- `TOOL-5` A tool's entrypoint -- what `{{tools:name}}` resolves to -- is the
`bin` resolved in order: an explicit `mind.toml` `[[items]].bin`, else `TOOL.md`
frontmatter `bin:`, else the convention default `<name>` (a file named after
the tool at the tool dir root, e.g. `tools/shard-plan/shard-plan`) when that
file is present in the source. A tool that nothing invokes as an executable
need not resolve a `bin`.
- `TOOL-6` Namespacing applies to tools as to any kind: a prefix gives the
effective name `<prefix>-<name>`, and a tool's stable identity is
`(source, kind, bare_name)` (namespacing.md), so a prefix change is a rename
matched on identity by `upgrade`/`introspect` (lifecycle.md).
- `TOOL-7` `mind.toml` accepts `kind = "tool"` wherever a kind is named
(`[[items]]`, and `[discover].tools` globs, which match the tool DIRECTORY
rather than an anchor file). The `bin` and `build` item fields are valid only on
a tool; on any other kind they are a `mind.toml` schema error.
## Path-reference tokens
An item references tooling by a token that expands, at install, to the tooling's
store path. Expansion runs in the same staging pass that expands `{{ns:}}`
(namespacing.md), so a bad reference fails before the live install is touched and
the recorded content hash is of the source (token) form.
- `TOOL-10` `{{self}}` in an item's text expands to that item's own store
directory (a path under the store root, `~/.mind/store` honoring `MIND_HOME`,
rendered with a leading `~` per TOOL-16). It is available in every kind, so a
skill addresses its own bundled resources as `{{self}}/resources/<script>`
without hardcoding its installed name.
- `TOOL-11` `{{path:ref}}` expands to a sibling item's store directory, for
reaching non-entrypoint files in a tool (`{{path:tool:x}}/lib/helper.sh`).
`ref` is a sibling's bare name, optionally kind-qualified
(`{{path:tool:detect}}`, `{{path:skill:review}}`). An unqualified `ref`
matching items of more than one kind is an error (`BadReference`); a `ref`
matching no sibling is an error (`BadReference`), as for `{{ns:}}` (NS-12).
- `TOOL-12` `{{tools:name}}` expands to a sibling tool's entrypoint: the tool's
store directory joined with its resolved `bin` (TOOL-5). The plural `tools:` is
distinct from the `tool:` kind-qualifier used in `{{path:}}`. A `name` that is
not a sibling tool, or a tool with no resolvable `bin`, is an error
(`BadReference`).
- `TOOL-13` Path tokens expand in the staging copy during the transactional
install, alongside `{{ns:}}` (NS-11). The recorded content hash is of the token
(source) form, not the expanded copy, so drift detection compares source with
source (NS-13). Both prefixed and unprefixed installs expand tokens (NS-14): a
store path is prefix-aware via the referent's effective name.
- `TOOL-14` Path tokens follow the same gate `{{ns:}}` does (NS-53, TOOL-19). A
token expands in a file when the file has a markdown extension, or the item
is of the `workflow` kind, or the file is on the item's NS-57 `expand:` list.
For a markdown item file, whatever the item kind, that covers the anchor file
of a skill or tool (`SKILL.md`, `TOOL.md`), an agent or rule's own file, or a
bundled markdown reference doc. Outside that gate -- a non-markdown bundled
file (a script, data) that belongs to a non-workflow item and is not
`expand:`-listed -- `{{tools:shard-plan}}` written in a bundled `pr.py` is left
exactly as written. The designed use of a
path token is prose (a skill telling Claude to run `{{tools:detect}}`), which
is why the narrowing costs nothing in practice for the ordinary case. Token
edge cases mirror `{{ns:}}` (NS-15): inner whitespace is trimmed
(`{{ path:x }}`); an unterminated token (no closing `}}`) is left verbatim; a
non-UTF-8 file is not scanned; text with no `{{` token is copied unchanged.
- `TOOL-15` Path tokens resolve within the source only (sibling scope), as
`{{ns:}}` does, so tool-to-tool and bundled-script-to-tool references resolve
when both items ship in the same source. Cross-source tooling references are out
of scope: ship a tool in the same source as the items that use it.
- `TOOL-19` Path tokens follow NS-53's gate: `{{self}}`, `{{tools:name}}`, and
`{{path:ref}}` all expand in a file when the file has a markdown extension (an
extension of `md`, `markdown`, `mdown`, or `mkd`, case-insensitively), or the
item is of the `workflow` kind, or the file is on the item's NS-57 `expand:`
list -- the same rule and the same chokepoint
(`namespace::item_expands_tokens`) `{{ns:}}` expansion uses. Combined with
TOOL-14, this is
the complete statement of where every token family expands.
- `TOOL-20` A path token in a file listed in an item's `expand:` frontmatter
(NS-57) does expand, overriding TOOL-19 for that file, and renders as an
absolute store path rather than the `~` form of TOOL-16. A `{{tools:name}}` in
a bundled `pr.py` listed under `expand:` expands to
`/home/user/.mind/store/tool/name/...`, not `~/.mind/store/tool/name/...`,
because the file is read by a program (here Python's `pathlib`) that does not
itself expand a leading `~`. The permission-glob rationale for TOOL-16 does not
apply: an `expand:` file is program input, not a command line a Claude
`settings.json` rule matches.
- `TOOL-16` A path token renders the store root with a leading `~` when the store
lies under the user's home directory (the default `~/.mind/store`): the home
prefix is written as a literal `~`, not spelled out absolutely. This keeps the
expansion matchable by a Claude `settings.json` permission glob, which uses
tilde syntax (`Bash(~/.mind/store/**)`) that an absolute path would not match.
When the store root is not under home (a `MIND_HOME` pointing elsewhere) or the
home directory cannot be determined, the token expands to the absolute path.
- `TOOL-17` A `{{tools:name}}` `BadReference` (TOOL-12) names its specific cause,
so a maintainer can tell a typo from a tool whose entrypoint failed to resolve.
A referent that matches no sibling tool -- a miss, or a name that is a non-tool
sibling -- reports "does not match any item", the wording every other reference
miss uses. A referent that names a real sibling tool with no resolvable `bin`
(TOOL-5) instead reports that the tool has no resolvable entrypoint. The cause
rides on both the install-time error and the `review` `bad-reference` finding
(CLI-135). The common trigger for the entrypoint case is a `TOOL.md` that
declares the `bin` but is gitignored or unadded, so it resolves in the author's
working tree yet is absent from a clone; `review` catches that ahead of time as
a distinct advisory (CLI-190).
- `TOOL-18` A `{{path:ref}}` (TOOL-11) `BadReference` likewise names its cause. A
bare `{{path:name}}` that matches more than one sibling across kinds with no
`kind:` qualifier reports "is ambiguous across kinds; add a kind qualifier"
rather than "does not match any item" -- the referent does match, it is just
under-qualified, so the miss wording would send a maintainer looking for a
missing item that exists. A referent that matches nothing keeps the miss
wording. The cause rides on both the install-time error and the `review`
`bad-reference` finding (CLI-135), the same threading as TOOL-17.
Because every token expands under `~/.mind/store`, an item's invocations of its
tooling share one stable path prefix regardless of agent home or prefix, so a
permission allowlist can target that prefix (a `Bash(~/.mind/store/**)` rule,
matched by the `~` rendering of TOOL-16) rather than chase per-item installed
paths.
## Build hooks for compiled tooling
Tooling that must be compiled or fetched before it runs uses a build step. The
expected default is the maintainer's project-level build (a Makefile or build
script) run via a source-level install hook (install-hooks.md, HOOK-50) at meld,
which produces the artifacts in the working tree; the tool directory is then
copied as the final state at `learn` (TOOL-1), no per-tool config needed. A tool
that wants its own isolated, rollback-safe build instead declares a per-item
`build` (install-hooks.md, HOOK-70..73), run in staging before the store swap;
`{{tools:name}}` (TOOL-12) then resolves to the built artifact.
## Relationship to existing mechanisms
- A skill's bundled helpers install as part of the skill directory: the whole
directory is the item, copied and linked into each agent home as one symlink, so
every file under it comes along. They may live anywhere within it (`resources/`
is a convention, not a requirement); `{{self}}` is what makes them addressable
under a prefix. Siloing a helper with the single skill that uses it is a
supported choice, distinct from the `tool` kind, which is for tooling
deliberately shared across items or shipped on its own. `review` may note a
helper duplicated across items as a `tool` candidate, but treats keeping the
per-item copy as equally valid (CLI-144).
- The executable bit on scripts is preserved by the copy into the store (the
install copies file permissions), so a `+x` helper stays executable without a
hook.