Files
2026-08-16 22:14:57 +02:00

323 lines
16 KiB
Markdown

---
name: create-ink-tool
description: >-
Use when creating, implementing, splitting, or reviewing a compiled,
permission-bearing executable intended for admission through Ink policy.
Produce one inspectable executable per authority boundary with discoverable
help/contract and falsifiable safety proof. Do not use for Ink's own commands,
ordinary or one-off CLIs/scripts, or skill authoring.
---
# Create an Ink tool
## One job
Design a **permission-sized executable** whose name and argv expose its reachable
reads and mutations so Ink can discover, digest-pin, and grant it without granting a platform.
One executable need not mean one source file: share private build-time modules
when that does not widen runtime authority.
## Inspect and decide
Read only what can change the boundary:
- the exact job and every reachable mutation;
- Ink's current specification, policy grammar, and mutation protocol;
- neighboring tools and existing executables that may already satisfy the job;
- resource, credential, selector, target, packaging, and proof contracts;
- repository specification and tests.
Project authority outranks this skill. Missing selector semantics, credentials,
recovery rules, or external contracts are blockers—not adapter opportunities.
```text
JOB -> REUSE? -> READS/MUTATIONS -> BOUNDARY -> CONTRACT -> ARTIFACT -> PROOF
```
1. Reuse a directly inspectable executable only when granting its whole reachable
surface is honest; otherwise build the coherent missing boundary, not a wrapper.
2. Enumerate reads, mutations, network calls, secrets, state, children, and
config/plugin discovery; split where approval, blast radius, or recovery differ.
3. Define argv, selectors, streams, errors, dependencies, exhaustive versus
bounded output, and any tool-owned semantic presentation without recreating
shell grammar or a host-wide mutation ontology.
4. State the runtime artifact honestly, then falsify its boundary, behavior,
presentation, and portability claims through Ink's real run entry point.
## Permission boundary
A digest proves **which executable** runs; it does not narrow that executable's
accepted argv, config, plugins, or helpers. Reuse is correct only when granting
the entire reachable surface is honest.
Split executables when approval, blast radius, credentials, selectors, drift, or
recovery differ. In particular:
- read and mutation are separate executables even when they share transport;
- upload and deletion split when recovery or approval differs;
- a generic request, cloud-admin, SDK, MCP, or `call NAME JSON` dispatcher is not
a permission boundary;
- one executable per endpoint is needless fragmentation;
- several read verbs may share one executable only when resource family,
credential scope, selectors, trust, and output contract are the same.
A script/dispatcher is the authority of its interpreter and every reachable
helper unless the host pins and revalidates all of them. Resolve first-party
helpers beside the script, constrain ambient lookup, and inventory dependencies.
If the whole boundary is too broad, admit the narrower child executable instead.
## Readers and selectors
A read executable must be structurally unable to mutate through any accepted
flag, subcommand, config, plugin, callback, helper, credential refresh, cache,
history, or telemetry path. For HTTP this normally means fixed GET/HEAD behavior,
not an arbitrary method flag. “Dry run” does not turn a mutator into a reader.
Use executable-level policy when every accepted invocation is one honest grant.
Add semantic selectors only for variable resources users may reasonably grant
differently, such as canonical path/root prefix, origin, repository, or account.
- Canonicalize selector values before access and matching.
- Prove an adjacent no-match causes no resource access.
- Do not expose invariant facts such as `action=read` or `method=GET`; the
executable already says that.
- Stdin-only transforms normally need no selectors because they acquire only
supplied input.
- A pattern is a selector only when two patterns are genuinely different grants,
not merely different queries below the same approved root.
Share read/mutation parsing or transport only through private build-time modules.
## Patterns and search
Shell globs, path filters, and content patterns are different contracts.
- Never reinterpret argv as shell syntax or perform ambient shell expansion.
- Accept a glob, fixed string, regex, or domain expression only through an
explicit mode such as `--include-glob`, `--fixed`, or `--ere`; never infer the
mode from punctuation.
- Keep matching as a separate stdin filter when all candidate bytes or records
have already been supplied.
- Put matching inside a resource reader only when it avoids opening,
transferring, traversing, or disclosing irrelevant resources inside that same
reader boundary. Canonical root/path selectors remain the authority boundary.
- Define one small established grammar. State root-relative versus basename
matching, separators, case/dotfile/symlink behavior, pattern combination,
include/exclude precedence, and invalid-pattern failure. Validate before access
where possible.
`find`/`fd` forms that execute or delete and `rg` forms that launch preprocessors
are not read boundaries merely because one intended invocation only searches. A
narrow native search tool is justified when it removes reachable mutations, adds
canonical path selectors, or supplies the required static portable artifact.
Implement the coherent missing subset, not a compatibility facade or renamed
wrapper.
## Mutators
Ink mutators use its elected semantic protocol:
```text
tool stage <operation argv...> # pure; emit pinned manifest
tool match <manifest> <selectors> # deterministic policy decision
tool apply <id> <hash> # revalidate; perform once
```
Required properties:
- `stage` performs no mutation and resolves no secrets;
- the manifest pins executable identity, canonical mutation, authority subject,
non-secret inputs, and drift-sensitive hashes;
- selectors are variable semantic facts, never argv prefixes, shell text, or
executable invariants;
- `apply` revalidates executable, manifest, policy snapshot, inputs, and drift;
- credentials resolve only at apply;
- retries cannot duplicate success; use upstream idempotency or reconciliation;
- an indeterminate upstream outcome is reported as indeterminate, never retried
blindly or called exactly-once;
- receipts are stable, secret-free, and sufficient for recovery;
- no direct mutation path remains beside the protocol unless project authority
explicitly places it outside policy.
## Semantic presentation
When Ink must show a domain operation better than raw stdout, the executable must
construct one canonical staged operation and derive a deterministic, bounded
**presentation projection** from it. The projection is not a second description:
the manifest binds the operation and exact semantic projection bytes. Ink owns
layout; the tool owns meaning.
Use the repository's elected projection envelope and bounds exactly; never invent
per-tool presentation formats. Within that contract, use a small display
vocabulary rather than universal mutation kinds:
- headline and canonical target;
- ordered key/value facts;
- bounded text or unified diff;
- outcome and optional evidence reference.
Filesystem, HTTP, cloud, and infrastructure tools express their own semantics
with those primitives. For example, an HTTP mutator supplies method, canonical
origin/path, bounded body summary, status, and final URL as facts; it does not ask
Ink to understand an `http` mutation type. An infrastructure tool supplies account,
resource, region, and requested change as facts; it does not create a renderer
branch for its provider.
The projection is presentation evidence, never authority:
- canonicalize argv and pinned inputs once into the staged operation;
- derive projection records purely from that value and hash their exact semantic
bytes with the manifest;
- render approval from the exact staged projection stored under that identity;
- apply accepts only staged id plus hash, never replacement target, body, mutation,
or projection inputs;
- the receipt identifies the exact staged manifest and may add only outcome and
evidence facts; it cannot rewrite approved records;
- final rendering reuses the staged projection and overlays only validated receipt
facts, so there is no independently generated “final projection” to drift;
- it grants no policy selector, credential, retry, or execution semantics;
- it is secret-free, width-independent, stable across locale/timezone, and
bounded with an explicit omission or evidence handle;
- hash semantic records, never width-, theme-, or expansion-dependent rows;
- tools never emit colors, cursor control, borders, wrapping, or terminal layout;
- legacy and ordinary commands without a projection remain valid and use Ink's
generic argv/stdout/stderr fallback.
Do not add presentation metadata when argv plus bounded output already answers
what happened. Do add it for permission-bearing operations whose approval would
otherwise expose manifests, hashes, encoded payloads, or provider internals.
## CLI and stream contract
`tool --help` is the tested human contract: mutations, argv, streams, ordering,
exits, environment/config precedence, credential timing, dependencies, pattern
semantics, and one realistic pipeline. Explicit help succeeds on stdout; usage
errors fail on stderr. Selector help gives value grammar, canonicalization, and a
least-authority policy row, including AND within one row and alternatives across
approved global and ancestor-project rows. Project rows are not automatically
trusted: Ink freezes the normalized effective policy and requires digest approval;
child/session policy and the host floor may only narrow it.
Keep argv unsurprising: options before operands, `--` ends options, `-` denotes a
natural stream, secrets never enter argv, and unknown, incompatible, or trailing
arguments fail. No hidden account, cwd, config, resource search, prompt, pager,
color, progress, or `/dev/tty` access. Stdout is records, stderr diagnostics;
records stream with bounded memory, ordinary SIGPIPE stays quiet, and machine
output is independent of display width, locale, and timezone unless documented.
Choose one output posture:
- **Exhaustive:** emit every record and let ordinary pipes narrow it; no hidden
limit, ranking, or cursor.
- **Bounded:** bound before generation, disclose omissions, and provide a stable
cursor, detail command, omitted count, or retrievable handle.
Line-oriented TSV contains no literal TAB, LF, CR, or NUL; use a documented
versioned codec or NUL-record mode only when round-trip values require it. JSON is
opt-in when structure is the payload, never the default adapter around records.
Add `tool contract` only for Ink capability discovery. Keep it deterministic,
versioned, and machine-readable, with exactly one row per variable selector and
none for invariants. Metadata never chooses policy defaults. Ink pins executable
and contract bytes; schema versions belong in output, not executable, policy, or
skill names.
Make the executable name and one-line job obvious on the collection's normal
install or help surface; do not create a parallel catalog that repeats `--help`
or `contract`. In a recommended Ink policy row, make text after `--` useful to
the provider: lead with one copyable invocation, then the job. For semantic
mutators advertise only `NAME stage ...` and state that Ink owns match/apply.
## Portability and artifact
Name the claim precisely:
- **POSIX-specified:** behavior is actually specified by POSIX;
- **POSIX-portable custom:** a custom CLI using portable POSIX facilities;
- **POSIX-shaped:** composes with POSIX streams but uses extensions;
- **platform-specific:** requires named platform facilities.
Cross-compilation proves compilation, not runtime portability. Test representative
targets and ordinary hostile conditions: missing HOME/config, non-writable cwd,
EOF/short I/O, non-TTY streams, interruption, and relevant locale/timezone
variation. Avoid assumptions about `/proc`, GNU userland, bash, writable state,
or fixed path lengths.
“Single executable” permits only documented runtime libraries; “static” permits
none. Inspect the produced artifact. Do not add daemons, plugins, sidecars,
interpreters, or generated-client machinery unless they are the named job.
## Proof
Use the smallest matrix that can falsify the actual claims:
- help/contract agree with accepted argv and selectors;
- main path and one forbidden near miss with zero unintended mutation;
- each reader selector has an adjacent no-match before access;
- each mutator has admitted, approval-required, refused, drift, duplicate, and
indeterminate/recovery outcomes as applicable;
- a projected operation renders the exact staged target and change before and
after apply, with only receipt-bound outcome/evidence added; mutation of stored
projection bytes, replacement apply inputs, malformed or oversized records,
secret-bearing data, and receipt identity mismatch all fail closed;
- exhaustive output preserves every fixture record; bounded selection proves its
bound, omission signal, and continuation/detail path;
- stdout/stderr redirection, `tool | head`, empty/malformed input, `-`, and a path
beginning with `-`;
- selected record codec edge cases, including TAB/LF/CR/backslash/NUL as relevant;
- clean environment, non-TTY, target runtime smoke, and artifact dependency
inspection;
- Ink discovers, pins, decides, and executes the tool end to end.
Do not build a universal harness. One meaningful forbidden-near-miss test is worth
more than broad ceremonial coverage.
## Refusals
Do not:
- force one source file when one executable is the requirement;
- wrap or partially clone a utility whose whole admitted surface already fits;
- combine read and mutation for code reuse;
- expose a generic request/admin/registry platform;
- invent invariant selectors, a universal mutation ontology, or executable-name
branches in Ink's renderer;
- let tools choose terminal styling or let presentation metadata grant authority;
- treat argv prefixes, globs, or regexes as canonical path authority;
- add pagination where a complete stream and pipes are simpler;
- dump bodies from a bounded selection or silently truncate a complete stream;
- claim read-only, portable, static, or exactly-once without falsifiable proof;
- register or recommend the tool before its help, boundary, near miss, policy,
composition, and target artifact are proven.
## Handoff
Report only:
- `job` and `boundary`;
- argv/stream/output posture;
- authority selectors and credential timing;
- artifact and runtime dependencies;
- proof through Ink's real run path, including staged-projection/receipt identity;
- remaining blast radius or untested target.
## Evaluation
- **Mutator:** an S3 uploader splits from deletion, uses `stage/match/apply`,
resolves credentials at apply, and proves duplicate and indeterminate outcomes.
- **Reader:** repository reads use canonical `path-prefix` plus an adjacent
no-access proof; executable identity makes `action=read` an invalid selector.
- **Reuse:** grant existing `grep` only if its whole surface is honest; otherwise
build the missing bounded reader, not a compatibility wrapper.
- **Pattern:** a reader may own a root-relative glob only to avoid access;
matching supplied records remains a stdin filter, not path authority.
- **Presentation:** an HTTP mutator derives method, canonical target, and bounded
body summary from one staged operation; Ink renders the exact stored projection
without an `httpsend` branch and overlays receipt-bound status/final URL only.
- **Presentation sprawl:** “support AWS changes” does not add an AWS mutation enum or
renderer branch; the permission-sized infrastructure tool projects account,
region, resource, requested change, outcome, and evidence through the elected
generic vocabulary.
- **Portability/sprawl:** a cross-build alone proves neither runtime portability
nor static linkage; refuse daemons, SDKs, dispatchers, and policy editing.