# The `rtk` prefix marks executable commands only, and is repo-wide **Status: accepted (2026-09-08).** ## Context `CLAUDE.md` states the org convention as a golden rule: "Always prefix commands with `rtk`. If RTK has a dedicated filter, it uses it. If not, it passes through unchanged. This means RTK is always safe to use." Issue #113 observed that the rule had never been written down for skill *prose*, where a `git ` mention can be either an instruction to execute or a reference to the concept, and that the corpus had drifted into carrying both spellings with no stated rule. PR #130 swept the `git` plugin and recorded a two-way split in `plugins/git/README.md`. Review found two defects in that sweep, and both are in the premise rather than the execution. **RTK is not output-transparent.** `rtk git --help` enumerates twelve filtered subcommands — `diff`, `log`, `status`, `show`, `add`, `commit`, `push`, `pull`, `branch`, `fetch`, `stash`, `worktree`. Everything else is a true passthrough. Inside that set the filter is not a formatting preference; it changes what the command *reports*. Measured against rtk 0.42.4: | Command | What rtk does to it | |---|---| | `worktree list --porcelain -z` | discards both flags; no NUL separators, no `locked`/`lock_reason` field at all | | `worktree list -v` | abbreviates `/root/…` to `~/…`, collapses column alignment | | `branch --list ` | emits a phantom `* ` line even on no match | | `diff --name-only` / `--name-status` | appends a blank line and a `Changes:` trailer | | `diff --word-diff[=color\|=porcelain]` | emits no `[-removed-] {+added+}` markers; substitutes a diffstat | | `log -L` | truncates each diff body line at ~72 characters with an ellipsis | | `stash pop` (on conflict) | prints only `FAILED: git stash pop`, swallowing `CONFLICT`, `Unmerged paths` and the retained-entry notice | | `stash list` (empty) | prints `No stashes` where git prints nothing | Every one of those falsified a skill that was written against the bare output. `git-worktrees`'s Step 2 required `locked` and `lock_reason` from a command whose rtk rendering has never carried them; `git-log-format.md` documented `[-removed-] {+added+}` markers beside a command that no longer produces them. The two-way split could not see any of this, because both halves of it are about what a *sentence* is doing and none of it is about what the *command* does. **The rule is not `git`-plugin-scoped.** `plugins/git/README.md` claimed the `gitea-*` skills "contain no `git`/`rtk` mentions at all". Five `gitea-*` SKILL.md files run `git remote get-url origin` in a fenced ```bash Step block — the README's own canonical example of "executable, instructed" — plus `git branch --show-current` in a reference file and three `git remote -v` in `gitea-orchestrate.agent.md`. A convention stated inside one plugin's README is invisible from the plugin next door, which is how those eight sites stayed bare through the sweep that existed to find them. ## Decision **One rule, three clauses, repo-wide** — every `plugins/*/.apm/skills/**` and `plugins/*/.apm/agents/**` file, not the `git` plugin alone. 1. **Executable and instructed → `rtk git`.** Anything telling the agent to run a command now: an imperative step, a dispatch-table "Run" cell, a fenced code-block procedure. `rtk git push -u origin `. 2. **Illustrative or referential → bare `git`.** Naming a flag's behaviour, quoting a doc's own heading, describing a command in the abstract, warning against an anti-pattern. "`git switch` refuses rather than clobbering conflicting local edits." 3. **Machine-parsed or interactive → bare `git`, and say why inline.** A command whose output the skill parses, where rtk is in the filtered set above; or a command that hands control to an interactive child process. Clause 3 is the new one and it looks arbitrary without the table in Context, which is why the measurements are recorded here rather than left in a PR thread. It is applied per subcommand and per flag, not per skill: `tag --list` stays prefixed because rtk passes it through byte-identically, while `branch --list` two words away goes bare because it does not. `git remote get-url origin`, `git remote -v`, `git branch --show-current`, `git log --oneline -1` and `git add -u` were all re-measured as byte-identical passthroughs and are therefore prefixed, parsing notwithstanding. Two consequences of that per-subcommand basis are worth stating, because both are load-bearing and neither is comfortable: - **rtk's filtered set is a moving target.** `git rebase` and `git mergetool` are passthroughs on 0.42.4 — verified under `script(1)`, both inherit a real TTY, contradicting an earlier report that they did not. They stay bare anyway, on the interactive limb: a token filter has nothing to offer a command that hands control to an editor, and the prefix would only buy exposure to whatever a later rtk version decides to do with those subcommands. The same reasoning makes the *inner* call in `` `rtk git remote add origin-push $(git config remote.origin.url)` `` bare while the outer stays prefixed — `config` passes through cleanly today, but its stdout becomes a remote URL that is then force-pushed to, and that is not a blast radius to lend to a future filter change. - **`branch --show-current` sits on the sharp edge.** It is in the filtered set, it is parsed, and it is prefixed — on a measurement, in a subcommand whose sibling `--list` is exactly the defect clause 3 exists for. If rtk's `branch` filter is ever extended, that is the first site to break. It is called out rather than hedged, because a rule whose exceptions are unrecorded is the state this ADR is replacing. **A clause-3 site says so inline, in a few words.** "bare, not `rtk`: rtk prints a phantom `* ` line even on no match". Without it the next sweep re-prefixes the command, which is how #113 recurs. **The rule lives here, and `docs/spec/gates.md` carries the gate.** `plugins/git/README.md` is reduced to a pointer. It had also cited `git-workflow/references/hard-rules.md` as a place the rule was written down; that file contains no occurrence of "rtk", and the citation is removed rather than repaired. **Clause 1 is enforced by a `check-rtk-prefix` pre-commit hook; clauses 2 and 3 are not enforceable and are not gated.** The hook checks the two places a `git` mention is unambiguously an instruction — a line in a shell-tagged code fence, and the opening backticked span of a "Run" column cell — and a deliberately-bare command opts out with the literal string `ADR-0023` on its own line. Its coverage limits are recorded in `docs/spec/gates.md`, not smoothed over. ## Considered options **Add `compatibility:` frontmatter to every skill.** These six plugins are installable by third parties, and a consumer who installs `git` from the marketplace has no `rtk` on their PATH. Every prefixed command in the corpus is a plain `git` invocation with a word in front of it, so the prefix is *droppable*: delete `rtk ` and the command is correct. A `compatibility:` line per skill would state that in a machine-readable field. Rejected on cost. It is 39 lines of frontmatter restating one sentence, it is preloaded into every agent's context every session under ADR-0020's budget — the field is not free the way a line in a doc is — and it has no consumer: nothing reads `compatibility:`, so the field would be a comment with a colon in it. The consumer situation is documented here and in `plugins/git/README.md` instead, which is where a human installing a plugin actually looks. The same two-line note is owed to the other five plugin READMEs and is not yet written. **Move rtk to the execution layer entirely.** Skills instruct bare `git` throughout; `CLAUDE.md`'s session rule handles prefixing at the point of execution. This is the strongest rejected option and it deserves the space: it closes the consumer gap and all eight output defects at once, because the executing agent knows what it is about to parse and the skill does not have to predict it. It also removes clause 3 entirely — there is nothing to except. Rejected because the prefix is lost wherever an agent copies a command literally, which is the common case for a fenced procedure block and the whole reason dispatch tables exist. The org convention's value is that the prefix is *already there* in the text the agent lifts; a rule that relies on the agent remembering to add it is the rule that produced the drift in the first place. Worth revisiting if rtk ever ships a shell shim, which would make the execution layer transparent and this trade different. **Keep the two-way split and fix the eight sites by hand.** Rejected: the split has no vocabulary for "this command is executable, instructed, and must still be bare", so the eight sites would be unexplained exceptions and the next sweep re-prefixes them. That is the failure this ADR exists to stop, not a smaller version of it. **Gate clauses 2 and 3 as well.** Rejected as undecidable. "Run `git switch `" and "`git switch` refuses rather than clobbering local edits" are the same token sequence; separating them is a judgement about what a sentence is doing. A gate that guessed would fire on correct content, and a gate that fires on correct content gets added to `SKIP`, which disarms clause 1 along with it. ## The boundary the rule does not decide Two shapes in the corpus resisted the two-way split. The three-clause rule resolves one and does not resolve the other; both are recorded so an author meeting a third one knows which kind it is. **`git-worktrees/SKILL.md`'s tracking row carries both spellings in one Run cell** — `rtk git worktree add --track -b /` — always correct. `git worktree add ` expands to exactly this. **Resolved: the clauses apply per mention, not per row, per cell or per file.** The first is the instruction (clause 1), the second names what the first expands to (clause 2), and one table cell can hold one of each. The rule needed no change; the *gate* did, and it checks only a Run cell's opening span for exactly this reason. **`git-submodules/references/setup-and-update.md:80` has a git command inside a quoted argument to another command** — `rtk git submodule foreach 'git pull origin main || :'`. **Not resolved: all three clauses describe a command the reading agent executes, and the inner `git pull` is not one.** It is the literal text of an argument that `git submodule foreach` hands to a subshell running inside each submodule's own working tree, where the local convention does not reach. The file already gets this right and already justifies it in prose two lines below ("the git calls in it are the submodule's own — that is the one place a bare `git` is correct"). **An author meeting this shape should do the same: leave the inner command bare and justify it inline.** It is deliberately not promoted to a fourth clause on one instance. The gate does not decide it either — it happens to pass this line, because the segment containing the inner command begins with `rtk`, and that is an accident of the split rather than an understanding of quoting. ## Consequences Eleven sites in `plugins/git/.apm/skills/**` revert to bare `git` under clause 3, each carrying a short inline reason. Eight sites across `plugins/gitea/.apm/skills/**` and `plugins/gitea/.apm/agents/gitea-orchestrate.agent.md` gain the prefix under clause 1, and one in `pc-run/SKILL.md` that the #130 sweep's grep missed because the backtick opens with `SKIP=` rather than `git `. `plugins/git/README.md`'s Conventions section becomes a pointer here, minus a paragraph that was false about the `gitea-*` skills and a citation to a file that does not carry the rule. A `check-rtk-prefix` pre-commit hook and `tests/test-check-rtk-prefix.sh` land with it; the test runs the gate against the pre-sweep corpus on `main` and asserts it fails there, because a gate that only passes on the fixed tree proves nothing about the drift it was written for.