Growing Outside In Systems
lexler/skill-factory
Drive feature development using Outside-In TDD with Hexagonal Architecture.
Canonical rules for writing tests in the Shift codebase. An agent skill from shift-editor/shift.
$ npx skills add shift-editor/shift --skill writing-tests -a claude-codeProject install by default; add -g for ~/.claude/skills/.
$ gh skill install shift-editor/shift writing-tests --agent claude-codeProject scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).
$ git clone --depth 1 https://github.com/shift-editor/shift.git skills-src && mkdir -p .claude/skills && cp -r skills-src/.codex/skills/writing-tests .claude/skills/writing-tests && rm -rf skills-srcUse ~/.claude/skills/ instead of .claude/skills for a personal install. The folder must contain SKILL.md.
Claude Code skills documentation · loads skills from .claude/skills/
Install the "writing-tests" agent skill from https://github.com/shift-editor/shift/tree/main/.codex/skills/writing-tests into .claude/skills/writing-tests/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "writing-tests", then confirm the skill loads.Claude Code copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$skill-installer install https://github.com/shift-editor/shift/tree/main/.codex/skills/writing-testsType this inside Codex. $skill-installer <name> installs a curated skill from openai/skills. The installer writes to $CODEX_HOME/skills (default ~/.codex/skills). Restart Codex if the skill does not show up.
$ npx skills add shift-editor/shift --skill writing-tests -a codexProject install goes to .agents/skills/; add -g for ~/.codex/skills/.
$ gh skill install shift-editor/shift writing-tests --agent codexProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/shift-editor/shift.git skills-src && mkdir -p .agents/skills && cp -r skills-src/.codex/skills/writing-tests .agents/skills/writing-tests && rm -rf skills-srcUse ~/.agents/skills/ instead of .agents/skills for a personal install.
Codex skills documentation · loads skills from .agents/skills/
Install the "writing-tests" agent skill from https://github.com/shift-editor/shift/tree/main/.codex/skills/writing-tests into .agents/skills/writing-tests/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "writing-tests", then confirm the skill loads.Codex copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add shift-editor/shift --skill writing-tests -a cursorProject install goes to .agents/skills/; add -g for ~/.cursor/skills/.
$ gh skill install shift-editor/shift writing-tests --agent cursorProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/shift-editor/shift.git skills-src && mkdir -p .cursor/skills && cp -r skills-src/.codex/skills/writing-tests .cursor/skills/writing-tests && rm -rf skills-srcUse ~/.cursor/skills/ instead of .cursor/skills for a personal install.
Cursor skills documentation · loads skills from .cursor/skills/, .agents/skills/, .claude/skills/, .codex/skills/
Install the "writing-tests" agent skill from https://github.com/shift-editor/shift/tree/main/.codex/skills/writing-tests into .cursor/skills/writing-tests/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "writing-tests", then confirm the skill loads.Cursor copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gemini skills install https://github.com/shift-editor/shift.git --path .codex/skills/writing-tests--scope user (default) or --scope workspace; --path is the subfolder of the repo that holds the skill; --consent skips the security confirmation prompt.
$ npx skills add shift-editor/shift --skill writing-tests -a gemini-cliProject install goes to .agents/skills/; add -g for ~/.gemini/skills/.
$ gh skill install shift-editor/shift writing-tests --agent gemini-cliProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/shift-editor/shift.git skills-src && mkdir -p .gemini/skills && cp -r skills-src/.codex/skills/writing-tests .gemini/skills/writing-tests && rm -rf skills-srcUse ~/.gemini/skills/ instead of .gemini/skills for a personal install, then run /skills reload.
Gemini CLI skills documentation · loads skills from .gemini/skills/, .agents/skills/
Install the "writing-tests" agent skill from https://github.com/shift-editor/shift/tree/main/.codex/skills/writing-tests into .gemini/skills/writing-tests/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "writing-tests", then confirm the skill loads.Gemini CLI copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gh skill install shift-editor/shift writing-testsInstalls for Copilot at project scope by default; add --scope user for a personal install. Preview a skill first with gh skill preview. Needs GitHub CLI 2.90.0 or later (public preview).
$ npx skills add shift-editor/shift --skill writing-tests -a github-copilotProject install goes to .agents/skills/; add -g for ~/.copilot/skills/.
$ git clone --depth 1 https://github.com/shift-editor/shift.git skills-src && mkdir -p .github/skills && cp -r skills-src/.codex/skills/writing-tests .github/skills/writing-tests && rm -rf skills-srcUse ~/.copilot/skills/ instead of .github/skills for a personal install. Commit .github/skills so cloud agent and code review can use it.
GitHub Copilot skills documentation · loads skills from .github/skills/, .claude/skills/, .agents/skills/
Install the "writing-tests" agent skill from https://github.com/shift-editor/shift/tree/main/.codex/skills/writing-tests into .github/skills/writing-tests/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "writing-tests", then confirm the skill loads.GitHub Copilot copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add shift-editor/shift --skill writing-tests -a opencodeOpenCode documents no install command of its own. Project install goes to .agents/skills/; add -g for ~/.config/opencode/skills/.
$ gh skill install shift-editor/shift writing-tests --agent opencodeProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/shift-editor/shift.git skills-src && mkdir -p .opencode/skills && cp -r skills-src/.codex/skills/writing-tests .opencode/skills/writing-tests && rm -rf skills-srcUse ~/.config/opencode/skills/ instead of .opencode/skills for a personal install.
OpenCode skills documentation · loads skills from .opencode/skills/, .claude/skills/, .agents/skills/
Install the "writing-tests" agent skill from https://github.com/shift-editor/shift/tree/main/.codex/skills/writing-tests into .opencode/skills/writing-tests/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "writing-tests", then confirm the skill loads.OpenCode copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
writing-testsCanonical rules for writing tests in the Shift codebase. An agent skill from shift-editor/shift.
Writing Tests is an agent skill from shift-editor/shift. Canonical rules for writing tests in the Shift codebase. Use whenever you add, rewrite, or review a .test.ts or .spec.ts file — or any time you're about to mock, stub, or spy your way around a testing problem. Covers TestEditor-based tests and desktop E2E tests driven through EditorDriver.
Its SKILL.md is about 5.3k tokens, which your agent loads only when the skill is triggered. It is a single SKILL.md file with no bundled scripts.
It sits in Testing & QA, covering End-to-end testing. It works with Rust and TypeScript. The repository describes itself as: A cross-platform font editor built in Rust and TypeScript. The licence is Apache-2.0.
3 steps, taken from the first numbered list in SKILL.md.
Read from SKILL.md and the folder at commit 6ade7f2. It shows what the files ask for, not the result of running them.
Pre-approves nothing: there is no allowed-tools line, so your agent's usual permission prompts apply.
From allowed-tools in the SKILL.md frontmatter.
Shell commands in SKILL.md call:
pnpmFrom the folder's file list and the shell code blocks in SKILL.md.
No URLs in SKILL.md. Its commands use pnpm, which can reach the network depending on how they are called.
From URLs in SKILL.md, links to its own repository left out.
Names no API keys, tokens, secrets or passwords.
From names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.
Writing Tests loads about 5.3k tokens when it runs. Until then it costs about 77 tokens; SKILL.md has 1,978 words of instructions outside code blocks.
Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.
The automated check found no risky patterns in SKILL.md.
Automated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); files beside SKILL.md are not scanned.
The full file from shift-editor/shift at commit 6ade7f2, republished under its Apache-2.0 licence (© shift-editor). 1,978 words, ~5,273 tokens.
.claude/skills/writing-tests/SKILL.md (or your agent's skills folder).The goal is tests that survive a full rewrite of the implementation, as long as the behavior stays the same. If your test breaks when a private method is renamed or a call count changes, you're testing the wrong thing.
Assert on observable state, not on mock calls.
Drive the code through its user-facing surface (editor.copy(), editor.click(), toolManager.handleKeyDown(...)) and assert on what a user would see — glyph contours, selection, viewport pan, tool state, command history, returned values. Never on "was method X called N times."
Everything else here is a consequence of that rule.
Do not treat line coverage or "every changed file needs a test" as the goal. Add a test only when it protects an observable behavior, domain invariant, or public boundary that could regress independently of the implementation.
Skip a new test when it would only:
Examples: do not test that a QueryClientProvider is mounted, that a query key contains a particular string, or that a fixed-size chunk helper calls slice() correctly. Do test stable batching if it is exposed as a reusable contract with edge cases, or test the user-visible result through a real integration boundary when one exists.
When no worthwhile automated test exists, say so explicitly in the handoff and record the focused manual verification performed. Do not create a low-value test to make the diff look complete.
Tests follow behavioral ownership, not implementation names. Removing or replacing a class does not make its tests obsolete when another class now owns the same behavior.
Before deleting a test or materially reducing a test file, create a test migration ledger. For every removed it() or test() block, record:
Move replacement tests to the new behavioral owner before deleting the old file. Similar-looking coverage is not enough: verify that it exercises the same boundary, failure mode, and state transition. Report removed and replacement test counts in the handoff.
Example:
| Removed test | Protected invariant | Replacement |
|---|---|---|
| Draft cancel restores rule-expanded points | Cancellation restores every previewed position, including positions outside the initial targets | GlyphLayerEdit.test.ts multi-position cancellation test |
| Next draft starts from committed preview | A finished edit becomes the base of the next interaction | GlyphLayerEdit.test.ts sequential edit test |
The moment you think "I'll just mock this out" is the moment to stop and look for prior art. Real Electron / font-editor / reactive-signal codebases have solved the same class of problem: VSCode, Obsidian, Signal Desktop, Bitwarden, Fontra, tldraw. Their patterns are on GitHub.
The SystemClipboard adapter in this repo came from a short WebSearch pass through VSCode's IClipboardService + TestClipboardService — not from inventing one locally. Ten minutes of searching usually beats an hour of mock wrangling, and the resulting test survives refactors because it mirrors a pattern that works in production code.
Rule of thumb: if the repo doesn't already show you a clean way to test this, the answer is probably "inject a boundary and test through it." Go find how someone else injected the boundary before reaching for vi.mock.
If you catch yourself doing any of the following, stop. You're about to write a test this codebase has deliberately deleted.
| Reaching for… | What it means | Do instead |
|---|---|---|
Vitest mock primitives — vi.fn, vi.spyOn, toHaveBeenCalled, mock.calls.length, .mock.calls, execution-order arrays (applied.push(...)) | Asserting on "was this called" instead of "what did the user see" | Find the user-facing consequence (state change, return value, emitted payload). If there isn't one, the behavior isn't worth a unit test. Exception: when you're testing a primitive whose contract is the invocation count (reactive library fire counts, pub/sub dispatch), a plain closure counter (let n = 0; ...; n++) is fine — don't reach for vitest mocks |
Hand-built events, states, or snapshots — constructing ToolEvents, Coordinates, GlyphSnapshots inline so you can pass them straight into a method | Recreating the pipeline instead of using it | Drive through TestEditor — real gestures, real events, real state |
Global stubs — vi.stubGlobal, monkey-patching window, requestAnimationFrame, electronAPI | The code has an unmanaged global dependency; stubbing papers over it | Inject the boundary. For rAF: toolManager.flushPointerMoves(). For IPC: SystemClipboard-style adapter. If the boundary doesn't exist yet, add one |
Parallel-world test harnesses — mock engines, mock renderers, mock command contexts, raw new Editor(...) in a .test.ts | Reimplementing production in TypeScript so the test can "look inside." Drifts from real behavior; the mock/prod divergence is the whole pain the repo swept out | Use TestEditor. If you can't, extract the logic into a pure function and test that. new Editor(...) in tests is enforced against by oxlint no-raw-editor-in-tests |
| Category | Use when | Template in repo |
|---|---|---|
| Tool test | User-triggerable behavior of a tool (pointer, keyboard, selection, cursor) | lib/tools/pen/Pen.test.ts, lib/tools/hand/Hand.test.ts, lib/tools/shape/Shape.test.ts |
| Command test | A Command's execute/undo/redo round-trip | lib/commands/primitives/PointCommands.test.ts |
| Pure module test | Stateless class or function with no Editor dependency | lib/tools/text/TextRunController.test.ts, lib/editor/hit/boundingBox.test.ts |
| Bridge test | NativeBridge against the real Rust engine | bridge/NativeBridge.test.ts |
| Desktop E2E test | Behavior requiring the real renderer, DOM, Electron, or visual output | e2e/editor.spec.ts, e2e/tools.spec.ts, e2e/application-menu.spec.ts |
If your target doesn't fit one of these, stop and ask — don't invent a new shape.
EditorDriver and Playwright. Load /writing-e2e-tests before writing it.TestEditor.Editor? → Pure module test.import { describe, it, expect, beforeEach } from "vitest";
import { TestEditor } from "@/testing/TestEditor";
describe("Hand tool", () => {
let editor: TestEditor;
beforeEach(() => {
editor = new TestEditor();
editor.startSession();
editor.selectTool("hand");
});
it("drag pans the viewport by the screen delta", () => {
const startPan = editor.pan;
editor.pointerDown(0, 0);
editor.pointerMove(50, 30); // crosses drag threshold
editor.pointerMove(120, 80);
editor.pointerUp(120, 80);
expect(editor.pan.x).toBe(startPan.x + 120);
expect(editor.pan.y).toBe(startPan.y + 80);
});
});import { describe, it, expect } from "vitest";
import { SnapPipelineRunner } from "./SnapPipelineRunner";
describe("SnapPipelineRunner", () => {
const runner = new SnapPipelineRunner();
it("point-to-point wins over a closer metrics candidate", () => {
const result = runner.runPointPipeline(
[
pointStep("p2p", hit("pointToPoint", { x: 110, y: 110 })),
pointStep("metrics", hit("metrics", { x: 101, y: 101 })),
],
pointArgs,
);
expect(result.source).toBe("pointToPoint");
expect(result.point).toEqual({ x: 110, y: 110 });
});
});Stub inputs (a PointSnapStep here is an interface — build a minimal one). Never stub the class under test.
import { describe, it, expect, beforeEach } from "vitest";
import { NativeBridge } from "./NativeBridge";
import { createBridge } from "@/testing/engine";
describe("NativeBridge session lifecycle", () => {
let bridge: NativeBridge;
beforeEach(() => {
bridge = createBridge(); // real Rust engine via shift-node
});
it("startEditSession populates $glyph", () => {
bridge.startEditSession("A");
expect(bridge.hasSession()).toBe(true);
expect(bridge.$glyph.peek()).not.toBe(null);
});
});import { workspaceTest as test, expect } from "./fixtures/electronApp";
test("moves a point and supports undo", async ({ editor }) => {
await editor.openGlyphByName("A");
const point = await editor.selectVisiblePoint();
const before = await editor.pointPosition(point.id);
await editor.dragPoint(point);
expect(await editor.pointPosition(point.id)).toEqual(point.expectedGlyphPosition);
await editor.undo();
expect(await editor.pointPosition(point.id)).toEqual(before);
});Use EditorDriver for semantic editor actions and domain observations. Keep assertions in the spec, and use Playwright's page only for visible UI, browser, or Electron behavior. Driver actions that can persist geometry own edit settling; confirmed reads such as outline() and pointPosition() must not be preceded by direct editCoordinator.settled() calls.
Coordinate spaces are explicit: pointer methods take page positions, dragCanvas() takes canvas-local positions, and projection helpers convert between scene, canvas, and page coordinates. activeGlyph() returns null until the scene node and authored layer are both published. After setup changes geometry or camera state, call waitForCanvasRender() before projections or pointer input; authored-layer readiness does not guarantee current canvas transforms or hit regions. Use livePointPosition() and selectionBounds() for gesture previews; keep pointPosition() for confirmed geometry.
Do not turn EditorDriver into a dumping ground. Native window/menu focus, variable-font construction, GPU residency, performance loops, and screenshot assertions remain at their owning fixture or spec boundary.
Before committing a test, run through these. Any miss means the test is wrong.
throw new Error("unimplemented"). Does your test fail? If it still passes, you wrote a tautology — usually because you asserted on a value you constructed rather than on a consequence.#private field or a tool-internal method.expect(editor.pointCount).toBe(4) — yes. expect(result).toBeDefined() or expect(spy).toHaveBeenCalled() — no, those survive any implementation.EditorDriver or the owning fixture.beforeEach is ≤ 5 lines: new TestEditor() + startSession() + selectTool() + optional fixture.TestEditor. If you need a reusable helper, it belongs as a method on TestEditor itself (see pointerMove, click, escape).beforeEach — don't construct glyph snapshots inline.editor fixture over constructing EditorDriver for the primary page. Construct a driver directly only for additional workspace windows.EditorDriver can express the same reusable boundary.pointerDown(), pointerMove(), pointerUp(), dragCanvas(), or cancelGesture().describe() names should explain the behavior or contract under test, not just repeat a class or file name.describe("glyph source geometry follows coordinate patches", ...) over describe("GlyphSourceState", ...).Some code resists clean unit testing — DOM event handlers, focus management, IME composition, React effect lifecycle.
Default move: extract the non-DOM logic into a pure function and test that. Example: HiddenTextInput keyboard handling → lib/tools/text/textInput.ts → handleTextKeyDown(event, editor). The component becomes a thin adapter, and the extracted function is a normal tool test via TestEditor.
When browser or Electron semantics are themselves the behavior, cover that thin adapter with a focused Desktop E2E test instead of recreating the environment with jsdom, global stubs, or IPC mocks. If extraction and a real E2E boundary still do not provide worthwhile automation, pause and ask before introducing new test infrastructure; otherwise record focused manual QA.
The codebase went through a deliberate sweep that deleted thousands of lines of mock-based tests. See commits 5f2f503, 876e542, 642ec99, bfea0b5, 3f3a6d6, fa8a829, bd07e9d, 3f3a6d6 Delete MockFontEngine. The motivating pain was real:
vi.stubGlobal("window", ...)) leaked across test files and hid unmanaged global dependencies.services.ts, 1,795 lines) became their own maintenance burden.The replacement — real Editor, real Rust via NAPI, fake only at the outermost boundary (SystemClipboard, NativeBridge) — catches regressions mocks silently missed. Don't reintroduce what was deleted.
Load /writing-e2e-tests for waits, oracles, goldens, fixtures, projects, and flake verification. The points below are the minimum.
Before adding or changing screenshot assertions, follow Desktop E2E capture determinism.
pnpm test # full vitest suite, runs against real Rust
pnpm test:watch # watch mode
pnpm test:e2e:visual # renderer, interaction, and software-rendered visual behavior
pnpm test:e2e:platform # native desktop and document lifecycle boundaries
pnpm test:e2e:gpu # hardware rendering and GPU residency behavior
pnpm typecheck # tsgo across all packages
pnpm lint:check # oxlint, includes test anti-pattern rulesRun the smallest relevant test command and file/title filter while iterating. For broad fixture changes, run the complete affected E2E project. Record exact commands, passed checks, and host-bound failures separately; never present a skipped or blocked native/GPU check as passing.
The relevant checks must pass before committing. If no-mock-call-assertions flags your test, it's caught you reaching for the banned pattern — fix the test, don't disable the rule.
© shift-editor, Apache-2.0. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file
Just SKILL.md in .codex/skills/writing-tests of shift-editor/shift.
Open the folder on GitHubat commit 6ade7f2
Writing Tests next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.
| Skill | Stars | Used in | Tokens | Auto-check | Licence | Repo updated |
|---|---|---|---|---|---|---|
| Writing Tests this skillshift-editor/shift | 347 | — | ~5.3k | Automated safety check: Pass | Apache-2.0 | |
| Growing Outside In Systemslexler/skill-factory | 239 | 1 repos | ~1.9k | Automated safety check: Pass | Apache-2.0 | |
| Testing Strategiesancoleman/ai-design-components | 526 | — | ~3.8k | Automated safety check: Pass | MIT | |
| Java SDK E2E Test with Replay Snapshotgithub/copilot-sdk | 11k | — | ~1.8k | Automated safety check: Pass | MIT | |
| RStudio Selenium to Playwright Migrationrstudio/rstudio | 5.1k | — | ~3.6k | Automated safety check: Pass | Custom licence | |
| TDDccusage/ccusage | 19k | — | ~665 | Automated safety check: Pass | Custom licence |
lexler/skill-factory
Drive feature development using Outside-In TDD with Hexagonal Architecture.
ancoleman/ai-design-components
Strategic guidance for choosing and implementing testing approaches across the test pyramid.
github/copilot-sdk
Creates a Java SDK end-to-end test for the Copilot SDK that runs against a recorded YAML snapshot through a replay proxy, so CI needs no real authentication.
rstudio/rstudio
Converts RStudio Python Selenium electron tests into TypeScript Playwright tests, checking each against a live RStudio before counting it as migrated.
ccusage/ccusage
Guides t-wada Red-Green-Refactor TDD for ccusage logic changes.
slopus/happy
Tests interactive CLI and TUI programs with Microsoft's tui-test, driving prompts, arrow keys and screen output in a real pseudo-terminal.
shift-editor/shift
Rules for writing git commits in the Shift font editor repo: Conventional Commits subjects, user-facing changelog wording, concise subjects and logical commit boundaries.
shift-editor/shift
Finds unused files, exports and class members with Knip, then verifies each candidate through reference tracing before removing anything, never using knip --fix.
shift-editor/shift
Updates or creates DOCS.md files for Shift subsystems, recording the architecture invariants and constraints that cannot be learned from reading the source.
shift-editor/shift
Fact-checks DOCS.md files against the source code, testing each concrete claim and sorting it as true, false, stale or unverifiable.
shift-editor/shift
Sets the rules for finding, writing and updating Shift GitHub issues: search for duplicates first, use outcome-focused titles and testable acceptance criteria.
shift-editor/shift
Guides writing JSDoc for Shift exported APIs as a stable caller contract, covering ownership, lifetime, side effects and nullability that TypeScript types cannot express.
Works with
Categories
Canonical rules for writing tests in the Shift codebase. An agent skill from shift-editor/shift. Writing Tests is an agent skill from shift-editor/shift. Canonical rules for writing tests in the Shift codebase.
Writing Tests fits situations like: review a .test.ts; .spec.ts file —; any time youre about to mock; spy your way around a testing problem.
Run `npx skills add shift-editor/shift --skill writing-tests -a claude-code`. Or copy the skill folder (.codex/skills/writing-tests in shift-editor/shift) into .claude/skills/writing-tests in your project. Claude Code loads it when a task matches its description.
Run `npx skills add shift-editor/shift --skill writing-tests -a codex`. Or copy the skill folder (.codex/skills/writing-tests in shift-editor/shift) into .agents/skills/writing-tests in your project. Codex loads it when a task matches its description.
Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add shift-editor/shift --skill writing-tests -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/writing-tests, .gemini/skills/writing-tests, .github/skills/writing-tests and .opencode/skills/writing-tests in your project.
Going by SKILL.md and its folder, Writing Tests needs the command-line tools its instructions call (pnpm).
SKILL.md contains no URLs. Any network use would come from the scripts or tools the agent runs. This is read from the text; nothing was executed.
Our automated static check of SKILL.md found no risky patterns, such as piping downloads into a shell, reading credential files or hidden Unicode. It is not a guarantee. Review the folder before installing.
Writing Tests is published under the Apache-2.0 licence (the repository's licence). It allows redistribution, so the full SKILL.md is shown on this page.
About 5.3k tokens (SKILL.md is roughly 21k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full.
Skills that share tags, products or a category with Writing Tests: Growing Outside In Systems (lexler/skill-factory, 239 stars), Testing Strategies (ancoleman/ai-design-components, 526 stars), Java SDK E2E Test with Replay Snapshot (github/copilot-sdk, 11k stars) and RStudio Selenium to Playwright Migration (rstudio/rstudio, 5.1k stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.
shift-editor (a GitHub organization) maintains it in shift-editor/shift, which has 347 GitHub stars. The repository holds 14 skills in this directory. The repository was last updated on October 7, 2026.
Source: shift-editor/shift on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.