Skip to content
Agentmart
Submit a tool

testing-policy

Install, migrate or refresh the canonical Testing Policy (Definition of Done) in the current project: the marked CLAUDE.md section, the generated reference…

Skill by Thalisu

IndexedMIT★ 1
Write a review

Install

Works with Claude Code, Claude apps, Codex, Cursor, Gemini CLI, GitHub Copilot, VS Code, OpenCode, Goose, Windsurf. Read the repository before you give any tool access to your data.

npx agentmarket install skill/thalisu-testing-policy

Asks which of your tools to add it to (Claude Code, Codex, Cursor, VS Code, Windsurf, Gemini CLI, Claude Desktop) and shows the change first. See https://agentmart.dev/cli.

Get the weekly diff

The weekly diff: one email a week with new agent tools, notable updates and grade changes, plus security alerts for tools you follow. Double opt-in, one-click unsubscribe.

We use your email only for this newsletter. Privacy

About

Install, migrate or refresh the canonical Testing Policy (Definition of Done) in the current project: the marked CLAUDE.md section, the generated reference file that carries the full rules, the unit-test-author and e2e-test-author agents, the inline test-author skill, the duplication scan and an optional hook that blocks new skip markers in test files.

Use cases: testing

No reviews yet. Installed it? Reviews come from verified installers only. How reviews work

Permissions

What this skill can do and what it asks for, from its SKILL.md. Checked Oct 25, 2026.

Tools
No tools found
Secrets
None asked for
Install scripts
None found
Runs as
Shell scripts

Network hosts it talks to 1

  • codeload.github.com · found in install command

Files it touches

  • ~/.claude/skills/testing-policy

What's inside

Skill name
testing-policy
Description
Install, migrate or refresh the canonical Testing Policy (Definition of Done) in the current project: the marked CLAUDE.md section, the generated reference file that carries the full rules, the unit-test-author and e2e-test-author agents, the inline test-author skill, the duplication scan and an optional hook that blocks new skip markers in test files.
License
MIT
Instructions
168 lines in SKILL.md · view on GitHub

Before installing

  • Ships executable scripts

Files (18)

  • AGENT-E2E.md
  • AGENT-UNIT.md
  • POLICY.md
  • SKILL-TEST-AUTHOR.md
  • SKILL.md
  • openai.yaml · agents/
  • forbid-test-skips.sh · scripts/
  • render-agent.sh · scripts/
  • render-policy.sh · scripts/
  • scan-test-assets.sh · scripts/
  • skip-patterns.sh · scripts/
  • verify-policy.sh · scripts/
  • author-tier.sh · tests/
  • handback-just-text.sh · tests/
  • partial-test-data.sh · tests/
  • policy-reference.sh · tests/
  • post-feature-gate.sh · tests/
  • unit-surface.sh · tests/

SKILL.md (first lines)

Install, migrate or refresh the canonical Testing Policy in this project.

## Sources and what they become

| Source (next to this file) | Installed as |
|---|---|
| `POLICY.md`, rendered by `scripts/render-policy.sh <surface>` | the marked `## Testing Policy (Definition of Done)` section in `CLAUDE.md`: the short core and Project facts |
| `POLICY.md`, rendered by `scripts/render-policy.sh <surface> --reference` | `.claude/testing-policy/policy.md`: the reference, the full rules under the section titles the core names |
| `AGENT-UNIT.md`, rendered by `scripts/render-agent.sh unit` | `.claude/agents/unit-test-author.md` |
| `AGENT-E2E.md`, rendered by `scripts/render-agent.sh e2e` | `.claude/agents/e2e-test-author.md`: native and mixed surfaces only |
| `SKILL-TEST-AUTHOR.md`, rendered by `scripts/render-agent.sh test-author` | `.claude/skills/test-author/SKILL.md`: the inline entry point for writers without the `Agent` tool |
| `scripts/scan-test-assets.sh` | `.claude/testing-policy/scan-test-assets.sh`: discovery and duplication scan |
| `scripts/skip-patterns.sh` | `.claude/testing-policy/skip-patterns.sh`: the skip markers and test-file classes shared by the scan and the hook; project additions live in `skip-patterns.local.sh` next to it |
| `scripts/forbid-test-skips.sh` | `.claude/testing-policy/forbid-test-skips.sh` + a `PreToolUse` entry in `.claude/settings.json`; optional, offered in step 6 |
| `scripts/verify-policy.sh` | not installed; run it from here in steps 0 and 8 |
| (no source, created on demand) | `.claude/testing-policy/capture/`: the home for anything captured from this project, committed with it like every other installed file |

Generated vs preserved. Everything rendered from a template or copied from `scripts/` is **generated**: overwritten on refresh, never hand-edited. That includes the reference, `.claude/testing-policy/policy.md`: it has no slots and holds no project fact, so nothing in it is the project's to keep, and a rule the project wants to add goes to Project facts. **Preserved** on refresh: Project facts, the agents' frontmatter (the author tier included) and Project map, `skip-patterns.local.sh`, `.claude/settings.json`. One addition is allowed in a preserved part: a line the template gained since the installed version (`verify-policy.sh` names it in `agent_*_map_missing=` or `policy_facts_missing=`; 2.2 added **System boundaries** and 2.4 added **Partial test data** to the unit map, and 2.5 added **Post-feature gate** to Project facts) is appended at its template position, **System boundaries** filled from this run's discovery and **Partial test data** from the outcome of step 5's offer, since discovery reads eligibility for that one and never a value, and **Post-feature gate** from the step-3 answer, since it is a choice no file in the project records. Otherwise existing lines stay verbatim, with two exceptions. The first: a **Partial test data** line the map already carries is rewritten when the offer was accepted this run and the add succeeded, the one case in step 5 where a value the project wrote moves. The second is a rename, on a unit surface only: 3.0 reworded two Project-facts lines for the single agent that surface installs, so the label `**Why the agents run in place**` becomes `**Why the agent runs in place**` where it stands, its filled text kept, and "the agents' Project map" on the **Duplication scan** line becomes "the agent's Project map". `verify-policy.sh` names the new label in `policy_facts_missing=` because it cannot tell a rename from a gain, and appending it would leave two labels for one fact. The report names the appended line and the renamed one. The template version lives once, in `POLICY.md`; every rendered file carries it (`v=` on the section's start marker and on the reference's `reference-start` marker, `<!-- testing-policy:agent v=… -->` / `<!-- testing-policy:skill v=… -->` after an agent's or the skill's frontmatter).

Three invariants. Every `{{slot}}` is filled from what exists in the project; never invent a path, a command or an example. Every command written into Project facts or a Project map was run once during this install and returned output. And nothing captured from a project is written outside it. Inside it, it is written to be shared: material that quotes real paths, service names, boundaries or debt goes to the agents' Project map, to Project facts, or to `.claude/testing-policy/capture/`, and is committed there, so a teammate reads it from git instead of re-running this install on their own machine. A Project map is **scoped to the repo the install ran in**; it never becomes a shared artifact of this skill, whose directory is common to every project and is public. That is the one absolute: this skill's own directory holds nothing captured from any project, committed or not.

## Step 0: detect state

Run `bash <skill-dir>/scripts/verify-policy.sh <project>`. `policy=` picks the mode:

- `none` → **install**
- `legacy` (a Testing Policy section without markers; `legacy_lines=` gives its extent, `EOF` when it closes the file) → **migrate**
- `stale` (marked, older version) or `drifted` (core hand-edited) → **refresh**
- `current` → nothing to do unless another line is not `ok` / `n/a` / `none` or the user asked to re-map: a `stale` or `drifted` agent is refreshed alone (step 5), a `wired-missing` hook re-copied (step 6), a `missing` or `drifted` `policy_reference` rewritten alone from the render with the section untouched (step 4, item 3), and a label named in `policy_facts_missing` appended alone at its template position with the core untouched (step 4; the unit label `**Why the agent runs in place**` is renamed in place instead), **Post-feature gate** with the answer step 3 asks for as it does on a refresh. Otherwise say so and stop.

Keep the rest of the output; steps 2-7 use it: `policy_reference` (printed only when a marked section exists: `missing` · `drifted` = any byte differing from `render-policy.sh <surface> --reference` · `ok`; written in step 4, and `missing` is the expected reading on a section installed before 3.0), `agent_unit` / `agent_e2e` (`missing` · `unmarked` = hand-written, ask · `stale` = older template, v2 marker style included · `drifted` = current version, core hand-edited · `ok`), `agent_unit_map_missing` / `agent_e2e_map_missing` (Project-map lines the template gained, appended in step 5), `agent_unit_tier` / `agent_e2e_tier` (`missing` = no `model:` line, the question never reached it · `invalid` = a value the harness does not accept · `ok`; asked in step 3, written in step 5), `policy_facts_missing` (Project-facts lines the template gained, appended in step 4, **Post-feature gate** asked in step 3), `skill_test_author` (`missing` · `stale` · `ok`), `scan_script`, `skip_patterns`, `hook` (`missing` · `script-only` · `wired` · `wired-missing` = wired in settings but the script is gone), `gitignored`, `capture_legacy`, `partial_data_helper` (`n/a` = not TypeScript · `absent` · `installed`, read in step 2).

## Step 1: the surface, native, consumer, mixed or unit

Infer before asking. Native evidence: a UI tree (`app/`, `pages/`, `screens/`, `src/app/`) and an E2E tool with flows in the repo (`playwright.config.*`, `.maestro/`, `cypress/`, `e2e*/`). Consumer evidence: an HTTP/queue/library entry point, no UI tree, no E2E tool, CLAUDE.md or docs naming the repos that call it. Mixed: both. Unit evidence: no user-facing surface of its own and no consumer repo whose E2E covers it, or the user saying the repo has no E2E tier; its unit suite is the whole proof.

- native → no question here.
- unit → no question here; step 2 skips every E2E read and step 5 installs no `e2e-test-author`.
- consumer or mixed → one `AskUserQuestion` (multiSelect) for the consumer repos: candidates from CLAUDE.md, docs, sibling directories and an existing Project facts consumer list, plus Other. When the inference itself is not conclusive, confirm the surface in the same call.

For each consumer, read its CLAUDE.md and scripts for: E2E tool · single-flow command · full-suite command · flow naming with 2-3 real file names · how its stack runs THIS repo from the local working tree · its known infra failures.

## Step 2: discover

1. Copy `scripts/scan-test-assets.sh` and `scripts/skip-patterns.sh` to `.claude/testing-policy/` (overwrite; both are generated) and run the scan with this project's roots: `--root` for unit roots, `--flows` for E2E roots (native/mixed), `--shared` for the shared homes you already recognise. Keep the whole report: `candidate-homes` seeds the role map; `duplicate-symbols`, `local-factories`, `inline-helpers`, `type-assertions` and `skip-markers` are the debt list for step 8.
2. Read package.json scripts (or Makefile / justfile / pyproject) for: unit full-suite and single-file commands, mandatory flags and preload files, formatter, E2E single-flow and full-suite commands, remote-runner conventions.
3. Build the role map. Unit: mocks · helpers · factories · fixtures; E2E (native/mixed): page objects or shared subflows · data factories · backend/DB helpers · fixtures. Exactly one candidate → record it. Two or more, or none → collect for step 3; never guess, never list both. One directory may hold two roles (a `data/` with factories and DB helpers): that is one path per role, not an ambiguity.
4. Unit boundaries. The `--section mock-targets` report lists every module-mock target, `package` or `internal`, with its file count. A boundary is what the tests replace that lives outside the repo: the `package` targets, plus whatever the mocks home stands in for (read its files: the exported `setup*` / `mock*` / `with*` functions and the service, client or clock each replaces). Each becomes one **System boundaries** line, `what → shared mock`. An `internal` target is one of two things, and the module's own imports tell which: a module that talks to the outside, one that imports a driver, SDK or client package (`ioredis`, `amqplib`, `jose`, `nodemailer`, `@supabase/supabase-js`) or calls `fetch`, the filesystem, the clock or `process.env` directly, is a thin wrapper around a boundary and is listed by what it wraps; a module that only imports this repo's other modules is an internal collaborator, **debt** for step 8, never a boundary. A boundary mocked inline per test file with no shared mock is listed as `none yet → create at <mocks home>/<name>` and counted as debt too. Only a read that stays inconclusive rides the step-3 question. No mocks at all → `none: pure modules`.
5. Record the idiom: identifier language in the test tree (it may legitimately differ from the code policy), mocking convention, locator strategy, base classes, and 2-3 real well-shaped shared assets with their signatures, as calibration, never a catalog.

   Calibration is scoped, never global. On **refresh** or **migrate**, the reference is this project's own installed `.claude/agents/*-test-author.md`: already filled, already about this repo. On a first **install** there is nothing to copy from, so the target is stated here instead of shipped as an example: one line per role, each naming a path that exists; boundaries written `what → shared mock`; 2-3 signatures rather than a catalog; counts wherever they were measured (`<n> of <total> files`, `<n> uses vs <m>`), never an adjective; a role with no home yet written `none yet → <path>` and carried into the debt list. A map captured from another repo is never the model: no example map ships with this skill, and none may be kept in its directory.
6. Preflight (native/mixed): app health endpoint, required services; include anything whose absence has produced a false product-bug red before (CLAUDE.md, docs, memory).
7. The partial data helper (unit, TypeScript only). Two facts already read decide eligibility, and both must hold: `partial_data_helper` from step 0 is not `n/a`, which is the TypeScript configuration, and the scan's `layout` section names at least one TypeScript test-file suffix under the roots, which is the test file. They are the whole gate: whether `type-assertions` found anything does not gate it, since the offer is about what the next test is written with, not about what the tree already carries. An eligible project reading `absent` carries the offer into step 3; one reading `installed` already has the helper and is not offered it. A decline leaves nothing behind: the next run reads `absent` again and offers again, the way the hook offer is offered again after a no. The offer says what a yes does before the answer is taken: it fetches third-party code from the public npm registry into the project, `@total-typescript/shoehorn` as a dev dependency of the workspace below, which every later test run then loads.

   Record for step 5 the workspace that holds the tests, the directory of the nearest `package.json` at or above the unit test root, and the manager that workspace implies. Every value the add takes from the project is untrusted input and none of it reaches a command line: the `packageManager` field, the lockfile it reads, the workspace path and the package-manager configuration that workspace carries. The manager is a name that selects one of four fixed commands and is never itself a command line: it comes from the `packageManager` field when present, read as its leading token up to the `@` and nothing else, else from the lockfile beside the workspace or at the repo root. The four commands are written out once, here, and step 5 runs one of them whole or runs nothing:

   - `pnpm-lock.yaml` → `pnpm add -D @total-typescript/shoehorn --ignore-scripts --ignore-pnpmfile --registry https://registry.npmjs.org/`
   - `yarn.lock` → `yarn add -D @total-typescript/shoehorn --ignore-scripts --registry https://registry.npmjs.org/`
   - `package-lock.json` → `npm install -D @total-typescript/shoehorn --ignore-scripts --registry https://registry.npmjs.org/`
   - `bun.lockb` or `bun.lock` → `bun add -d @total-typescript/shoehorn --ignore-scripts --registry https://registry.npmjs.org/`

   The flags are the gate, not decoration: the add is the one step of this install that pulls code from outside the repo, and a clone gets to configure how its package manager behaves. `--ignore-scripts` keeps the fetched package's own install hooks from running as the developer, and `--ignore-pnpmfile` keeps pnpm from loading the `.pnpmfile.cjs` a repository may carry, which runs even with scripts off. The pinned `--registry` is the one host the add talks to and the only one its credentials go to, so a `.npmrc` a clone wrote can neither name that host nor expand a variable of the developer's environment into a token for it.

   A flag closes the key it names, and such a file carries keys no flag on the line covers, a proxy and a CA among them, so the file itself is refused rather than argued with: when the workspace, or a directory between it and the repo root, carries `.npmrc`, `.yarnrc`, `.yarnrc.yml`, `.pnpmfile.cjs` or `bunfig.toml`, the add is not run at all and step 5 records it as debt like any other add that could not run. Whatever the add does resolve against, the registry and the credentials are the developer's own, never the project's.

   A `packageManager` whose leading token is not `pnpm`, `yarn`, `npm` or `bun`, neither source answering, or two workspaces holding tests, is the ambiguous case step 5 records as debt; never guess a manager or a workspace, and never run a string a project file supplied.

8. The author tier, for each author the install writes (`unit`; `e2e` on native and mixed) whose tier step 3 will ask: install, migrate, and an agent reading `agent_*_tier=missing` or `invalid`. The Recommended pair is derived from counts step 2 already measured, never from a new read. The author runs the reuse audit, decides what earns an assertion, and writes the diagnosis a handback routes on, and a wrong test is paid twice under red-first, so the Recommended model is `opus` and the volume of dispatches is paid down through the effort ([ADR 0052](../../docs/adr/0052-the-test-authors-carry-the-model-and-effort-the-project-picked-at-install.md)):
   - unit: `opus · medium`; `opus · high` when the scan's `duplicate-symbols` or `local-factories` report debt, or the shared homes hold more than a handful of assets, since that is where the reuse audit is hard. The cheaper options step 3 lists beside it are `fable · medium`, offered wherever a smaller model at a higher effort would otherwise run, and held at `medium` because at `low` it calls search tools less often and the reuse audit is a search ([ADR 0058](../../docs/adr/0058-the-author-tier-offers-fable-at-medium-and-never-at-low.md)), and `sonnet · high`.
   - e2e: `opus · high` on every project. When the preflight in item 6 names stateful services (a database, auth, seeds) the flows depend on, the option's description quotes them as the one reason a user may answer `opus xhigh` in free text, and says that step-up is unmeasured: `xhigh` is reserved for work where a quality gain was measured, and the model thinks noticeably more per turn there ([ADR 0059](../../docs/adr/0059-the-e2e-author-is-recommended-at-high-effort-and-xhigh-is-never-recommended-unmeasured.md)).

   Keep the counts that decided it: the option's description in step 3 quotes them.

On **refresh**, run the same discovery and diff it against the written Project facts / Project map: disagreements are reported, the written text stays untouched. The user decides.

## Step 3: ask, once

One `AskUserQuestion` call, holding the pending questions in this order:

1. The roles: one question per ambiguous or orphan role from step 2, with the real candidates found; for an orphan, a path following the project's own naming as the Recommended option, plus Other.
2. The mock targets: one multiSelect over the `internal` mock targets from step 2 whose read stayed inconclusive, asking which of them wrap a boundary; the rest are debt.
3. The legacy rules, in **migrate** mode only: one question per project-specific rule found in the legacy section that has no home in the template, with two options, keep it in Project facts, or drop it.
4. The post-feature gate, the tier that runs once a feature's own tests are green. Pending on install and migrate, and on a refresh only when `policy_facts_missing` names **Post-feature gate**. Four options, the full unit suite, the full E2E suite, both, or none; two on a unit surface, the full unit suite or none. Recommended: a refresh from before 2.5 marks `both`, the closest to the full unit suite per change and full E2E suite per phase that version stated; a migrate marks the one the legacy section states, if any; an install marks none, since the cost of a full suite is the project's trade-off alone.
5. The author tier from step 2, one question per author whose tier step 2 item 8 marked for asking: install, migrate, and an agent reading `agent_*_tier=missing` or `invalid`. Four options, plus the free-text Other the tool adds:
   - the Recommended pair, its description quoting the counts that decided it; an agent reading `invalid` marks its installed pair's correction as Recommended and names the value refused;
   - `fable · medium`, described as often competitive with Opus and Sonnet on cost per task while scoring higher, and held at `medium` because at `low` it searches less and the reuse audit is a search;
   - `sonnet · high`, described as the cheapest pair, at half the Opus price per token, and unmeasured on the reuse audit and the handback diagnosis, the two jobs a wrong test is paid twice for;
   - `inherit`, the session's model and effort.

   A free-text answer is read as `<model> <effort>` or `<model>` alone, and is taken only when `render-agent.sh` accepts it (the model one of `sonnet`, `opus`, `haiku`, `fable`, `inherit`; the effort one of `low`, `medium`, `high`, `xhigh`, `max`); the renderer's refusal is quoted and that one question asked again.
6. The partial data helper offer from step 2, for an eligible project only.
7. The hook offer from step 6, pending in the states that step offers it in, `hook=missing` and `script-only`.

The tool takes at most four questions per call, so the pending questions go in the order above, four per call: the first four ride in this call, the next four in a second call right after it, and so on, each question asked once. Both offers take their place in that order like any other question. Roles that mapped cleanly are never asked, and a project step 2 found ineligible never sees the offer and never sees the package name, so a Python or Maestro project is never asked about a TypeScript package. Skip the call entirely when nothing is pending.

## Step 4: render and place the policy

1. `bash scripts/render-policy.sh <surface>` and fill the `{{slots}}` in **Project facts** only; the core has none, and neither has the reference of item 3. `SCAN_COMMAND` is the exact invocation from step 2. `WHY_IN_PLACE` states the project's real reason (the E2E stack serves the primary checkout; the unit runner's path-ignore for agent worktrees; ...). `POST_FEATURE_GATE` is the step-3 answer as given, with the full-suite command of each suite it names.
2. Place it:
   - **install**: after the section documenting test commands when one exists, before conventions. Never inside a block another skill or generator owns (a marked `<!-- <name>:*-start -->` ... `<!-- <name>:*-end -->` region, or similar); put it between blocks, so regeneration cannot overwrite it.
   - **migrate**: replace the legacy section (`legacy_lines=` from step 0) in place, same position. Slot values come from the legacy text where it named them (commands, flow examples, infra failures); the project-specific rules the user kept go into Project facts.
   - **surface change** (the user asks for another surface, or discovery disagrees with `surface=` and the user confirms): render the new surface, replace the core, set `surface=` on the start marker, rewrite the reference for the new surface (item 3; its marker carries `surface=` too and its sections differ per surface), and drop the Project-facts lines the new surface does not render; every other facts line stays verbatim, and a **Post-feature gate** naming a suite the new surface lacks is asked again in step 3. The two unit wordings in the refresh bullet below follow the surface: a change into `unit` applies them, a change out of it reverses them, each in place with the filled text kept. The `test-author` skill is re-rendered for the new surface (step 5). An `e2e-test-author` left behind on a unit or consumer surface is deleted with the user's yes.
   - **refresh**: replace only `core-start` .. `core-end` with `render-policy.sh <surface> --core-only`, and set `v=` on the start marker to the template version. Project facts are preserved verbatim, except a label named in `policy_facts_missing`, which is appended at its template position: **Post-feature gate** takes the step-3 answer. Then rewrite the reference (item 3). From 2.10 that is the whole path to 3.0: the core between the markers becomes the short one, the start marker reads `v=3.0`, the reference is written for the first time (`policy_reference=missing` beside `policy=stale` is the expected reading before it), and Project facts stay verbatim, since the full rules that left the section are in the reference under the same titles and no project value was among them. A unit surface takes two more edits, both in Project facts and both in place: rename the label `**Why the agents run in place**` to `**Why the agent runs in place**`, keeping the text the project filled after it, and change "the agents' Project map" to "the agent's Project map" on the **Duplication scan** line. `policy_facts_missing` names the new label there; it is this rename, never a line to append.
3. Write the reference, in every mode: `bash scripts/render-policy.sh <surface> --reference > <project>/.claude/testing-policy/policy.md`, the whole output verbatim, the `reference-start` and `reference-end` markers included, overwriting the file when one is there. Nothing is filled in and nothing is edited afterwards: the reference is generated and slot-free, `verify-policy.sh` compares it byte for byte with that render, and every refresh overwrites it. The same write is the whole repair when step 0 read `policy=current` with `policy_reference=missing` or `drifted`: rewrite the file from the render, never patch it by hand, and leave the section alone. On `drifted`, say in the report that the file was rewritten, since a hand edit in it is lost; a project-specific rule belongs in Project facts.

## Step 5: agents and the inline skill

- `unit-test-author` always; `e2e-test-author` on native and mixed; the `test-author` skill always, rendered for the policy's surface (`TEST_AUTHOR_E2E_LINE` names the consumer repos on a consumer surface; the unit surface renders a unit-only skill with no E2E argument and no slot).
- Render with `bash scripts/render-agent.sh <unit|e2e|test-author <surface>>`, an agent with its step-3 tier: `--model <m> --effort <e>`, or `--model inherit` alone, which writes `model: inherit` and no `effort:` line, since omitting it is how an agent inherits the session's effort. What happens next follows the state from step 0:
  - `missing` → write the whole render, Project map slots filled from the role map. The Discovery block is the step-2 scan invocation (`--section duplicate-symbols`, plus `--section local-factories` for unit / `--section inline-helpers` for E2E) and 1-2 targeted greps over the shared homes, every line run once.
  - `stale` or `drifted` → replace only the core: from the line that starts with `<!-- testing-policy:core-start` (in the v2 style that comment spans two lines; replace through its `-->`) up to `<!-- testing-policy:core-end -->`, with `render-agent.sh <unit|e2e> --core-only`; add `<!-- testing-policy:agent v=<version> -->` right after the frontmatter when absent. Frontmatter and Project map stay verbatim, except a line named in `agent_*_map_missing` (for 2.2, **System boundaries** in the unit map; for 2.4, **Partial test data** in the unit map), which is appended at its template position with every other line left as the project wrote it. **System boundaries** is filled from step 2, and **Partial test data** from the outcome of the offer below, since step 2 reads eligibility for it and never a value. A map that already carries **Partial test data** is never named there, so nothing is appended to it: that line is the one exception below, and every other installed line stays verbatim. Report discovery disagreements (step 2).
  - An agent whose frontmatter lacks the tier or carries an invalid one (`agent_*_tier=missing` / `invalid`), in any state that keeps the frontmatter, gets its `model:` and `effort:` lines written from the step-3 answer, replacing any it had, with every other frontmatter line left as the project wrote it. On `ok` the tier is the project's and is never asked or rewritten, unless the user asks to change it.
  - `unmarked` (hand-written, no markers) → ask before touching it; never overwrite silently.
  - `ok` → nothing.
  - The `test-author` skill has no preserved part: `missing` or `stale` → write the whole render with its one slot filled.
- The unit map's **Partial test data** line is written here, because its value is the result of the add. On `missing`, and on a `stale` or `drifted` agent whose map lacks the label and has it appended, the line is written from the outcome below, whatever that outcome is. On a `stale` or `drifted` agent whose map already carries the label, the line is rewritten only when the offer was accepted this run and the add succeeded, and otherwise stays exactly as the project wrote it on every run that added nothing, so a refresh never overwrites a value the project set. On a yes at step 3, print the exact command line first, then run the add once from the workspace step 2 recorded: the whole fixed command that workspace's manager selected in step 2, its flags included, and nothing else. Nothing is appended to that line and no flag is dropped from it, since the flags are what keeps the add from running code the repository chose; the line is the fixed command a manager's name selected rather than any string a project file spelled. On success write the line as `fromPartial()` for partial data that still type checks and `fromAny()` for data that is wrong on purpose, from that package. Otherwise write `none yet → <that command>`, a pointer and not a command that ran: on a no, on an eligible project that was never offered, and on an add that failed or could not run at all (no network, the manager or the workspace unanswered, two workspaces holding tests, a workspace carrying package-manager configuration of its own). A failed add never stops the install; it is one more line of debt, and step 8 reports it with its reason. A project reading `partial_data_helper=n/a` at step 0 gets `n/a` on the line, so the label is filled in every project and in every mode.

## Step 6: enforcement hook

From Thalisu/SKILLS, MIT.

More from Thalisu/SKILLS

Signals

Stars
1
Forks
0
Open issues
0
Last commit (this folder)
Oct 17, 2026 (11 days ago)

From GitHub (fetched Oct 24, 2026).

Details

License
MIT
First seen
Oct 24, 2026
Last checked
Oct 24, 2026

More from Thalisu

Cite this page

testing-policy skill. Agent Mart. https://agentmart.dev/skills/thalisu-testing-policy (last updated 2026-10-24)