← Files AkinatorARCHIVED FILE
rules/12-artifacts-that-travel-name-nothing-local.md
9.85 KB · Oct 5, 2026 · 18:32 UTC
# Rule 12 - An artifact that travels names nothing only its birthplace has
## Purpose
Akinator's whole premise is that a document asserting things that are not there
is a critical defect. It was shipping 22 of them per install.
Every file in the Codex pack carried a banner naming its generator:
```
Generated by `scripts/build_codex_pack.py` from `skills/everything/references/akinator-plan.md`.
See `rules/07-codex-pack-is-generated.md`.
```
All three of those paths are real - **here**. The installer copies those files
into other repositories, where none of them exists. A bare repository that ran
the old Codex install script and then the coverage checker got **22 HIGH
findings on its first run**, every one on a file the plugin had just written.
The portable contract failed too, on the subtler half: `build_codex_pack.py` is
not a path, but it is still a file the host repository does not have.
It came back twice more, and each time outside this rule's old scope:
- **The Cursor router copy.** The README told Cursor users to copy
Akinator's own Cursor router into their repository - a file naming 21 paths
that exist only in Akinator's checkout. It was never in scope, because the
scope listed only the Codex pack.
- **The tool paths.** The skill's procedure ran its tools by repo-only paths
(the ledger tool under this checkout's `scripts/` directory), inside fenced blocks the body test
exempted as illustrative. A command an agent is told to run is an
instruction, not an illustration, and in a host repository it failed.
Evolved on 2026-09-18 (the recorded distil decision in
`.ai/ledger/decision/distil-a-generated-artifact-that-travels-named-56871dcd648a.md`)
rather than adding a rule beside it: three sightings, one defect.
The reason this survived every test is worth more than the bug. Twenty-one tests
covered the pack, including one named
`test_portable_contract_names_no_repo_relative_paths`. All of them looked at the
pack **from inside this checkout**, where every path resolves. None looked at it
from where it lands. A claim is only true relative to a tree, and a test that
never changes trees cannot check one.
## Applies to
- **In scope:** everything the installers copy - the one skill
(`.agents/skills/akinator/` with its references and tools), the portable
contract `.agents/AGENTS.md`, and the Cursor rule `.agents/cursor/akinator.mdc`
- and any future artifact installed into another repository. Also in scope:
the canonical skill's references under `skills/everything/references/`, which
travel inside the Claude plugin and are projected into the pack. The scope is stated as what ships, not as what might one day
ship, because a rule claiming more than its mechanism covers is the shape of
defect this rule exists to stop.
- **Out of scope:** files that stay here. `context/stack.md` and
`context/components.md` name their generators by path and should, because the
generator is in the same tree and the reader can run it.
- **Out of scope, deliberately:** `templates/**`. The installers do not copy it
- a target repo gets the pack and the contract, nothing else - and a template
naming `scripts/<extractor>.py` is a placeholder the host fills in, not a
claim. If templates ever start being installed, they come into scope and need
the same test.
## Mandatory rules
1. A travelling artifact **names no file by path or by filename** - not its
generator, not its source, not a rule it obeys. Every such name is a claim
about a tree the author has never seen.
2. It states instead **where it came from** and **how to get a fresh copy**.
Both halves. An origin with no refresh path is a dead end, and a stale copy
nobody can update is no better than a generator that is not there.
3. Its correctness is tested **from the destination** - installed into a scratch
repository, then checked - not only from this one.
4. Where the artifact must reference this repository's own conventions, it
describes them ("this repository's rules directory, whatever it is called")
rather than naming a path, and defers to the host's names. See
`rules/04-routers-stay-thin-and-synced.md` on adopt-never-impose.
5. This binds the **body** as well as the banner. Fixing only the banners left
twenty path references in eleven skill bodies - `templates/adr.md`,
`evals/newcomer/README.md` and friends - each of them a file that exists only
in an Akinator checkout, inside a document whose purpose is to be read
somewhere else. A skill that needs to point at Akinator's own material
**describes** it ("Akinator's ADR template") instead.
6. A tool the skill tells an agent to run is invoked as
`<skill>/scripts/<tool>.py`, where `<skill>` is the skill's own directory -
**wherever it appears, fenced or not**. A command is an instruction, not an
illustration; the fence exemption for illustrative paths never covers it.
7. A path relative to the travelling file that lands **inside the pack** -
a reference's link up to the skill's own SKILL.md, or the skill's link down
to its procedure reference - is allowed, because it travels
with the file and is true wherever the file lands.
## Prohibited patterns
```
<!--
GENERATED FILE - DO NOT EDIT BY HAND.
Generated by `scripts/build_codex_pack.py` from `skills/everything/references/akinator.md`.
See `rules/07-codex-pack-is-generated.md`.
-->
```
Three claims, all false wherever this file actually lives.
```
<!--
Regenerate with `build_codex_pack.py`.
-->
```
Still wrong, and harder to spot. Dropping the directory does not make the file
exist in the host repo - it only makes the falsehood shorter. This exact form
shipped in the portable contract *because* a test filtered on `"/" in name`.
```
<!--
Installed from the Akinator plugin.
-->
```
Wrong in the other direction: honest about origin, useless to anyone holding a
stale copy.
## Correct pattern
```
<!--
DO NOT EDIT BY HAND.
Installed from the Akinator plugin - the canonical akinator-plan skill.
No generator is named by path: this file travels into repositories
that do not have one, where naming it would be a false claim.
To update: reinstall Akinator, or regenerate inside an Akinator
checkout. Local edits here are replaced either way.
-->
```
The origin is named. The refresh path is named, in both the host case and the
checkout case. Nothing is asserted to exist in the tree holding the file - and
the banner says *why*, so the next contributor does not "helpfully" add the path
back.
## Enforcement
- Mechanism: `tests/test_codex_pack.py::test_the_installed_pack_leaves_a_target_repo_clean`
- writes the whole pack (skill, portable contract, Cursor rule) into a scratch
repository containing nothing else, where each lands, and runs the coverage
checker there, failing on any finding at medium or above. This is the test
that changes trees, and it is the one that matters.
- Mechanism: `tests/test_installer.py` - runs the **real** installers (sh
everywhere, PowerShell on Windows) into a scratch repository with a stub
claude, then runs the strict checker there.
- Mechanism: `tests/test_codex_pack.py::test_pack_banners_name_no_file_the_host_repo_will_not_have`
- asserts no banner in the plan names any file, by path or by bare filename.
- Mechanism: `tests/test_codex_pack.py::test_pack_bodies_name_no_path_the_host_repo_will_not_have`
- the same property for the **bodies**, which was the larger half: fixing the
banners left twenty path references across eleven skills. Fenced blocks are
exempt, because an illustrative path inside an example reads as illustrative;
a path that resolves inside the pack is allowed.
- Mechanism: `tests/test_codex_pack.py::test_every_markdown_link_in_the_pack_resolves_inside_it`
- every link in a packed `.md` or `.mdc` file resolves to another file of the
pack.
- Mechanism: `tests/test_plugin_structure.py::test_the_skill_runs_its_tools_from_its_own_folder`
- tool invocations use `<skill>/scripts/<tool>.py`, fenced or not.
- Mechanism: `skills/everything/scripts/akinator_coverage.py` - `check_generated`, whose vendored
branch requires an origin **and** a refresh path, and still fails any banner
that names a generator which does not resolve; `.mdc` files are scanned, so a
Cursor rule counts as a router and is link-checked.
- Type: unit and integration tests, run with the suite and in CI.
- How it fails: the first names the check, path and message of every finding the
install would produce; the second names the offending file and token.
- Last observed passing: 2026-09-18
**Never a git hook** - see `rules/05-no-git-hook-complication.md`.
## Exceptions
None. If a travelling artifact genuinely needs to point at something, it points
at it by description, or at a stable external URL, never at a repository path.
## Related
- Ledger: `.ai/ledger/failure/a-generated-artifact-that-travels-named-56871dcd648a.md`
- Memory: `memory/2026-08-30-a-claim-is-only-true-relative-to-a-tree.md`
- Docs: `docs/adr/0007-vendored-artifacts-declare-origin-not-generator.md` - why
the checker gained a vendored branch instead of skipping `.agents/`
- Docs: `docs/adr/0009-one-skill-one-command-one-installer.md` - the one skill
and the one installer whose copies this rule now scopes
- Evals: `evals/results/2026-08-30-06-anti-gaming.md` - the red-team run that
found this, twice, independently
- Rules: `rules/07-codex-pack-is-generated.md` - the pack is generated;
this rule constrains what the generated form may say
- Rules: `rules/11-invariants-ship-with-a-mutation-test.md` - the same
asymmetry, one level up: a test that cannot fail proves nothing
## Definition of done
- [x] The constraint is stated as a testable proposition.
- [x] The enforcement mechanism exists in the tree and is named by path.
- [x] The mechanism is not a git hook.
- [x] Prohibited and correct patterns are shown, including the near-miss form.
- [x] The exception path is named - here, that there is none.
- [x] The rule is indexed and reflected in every router.
SHA-256: 9c318c2bc22d09837d28ff606bb5b87b091b736c5324f7bba1329c280a1cf8b3