vinta-derive-skills · git:20261103.6a7c968 · 2026-11-03 · sha256 e3b7fe997829ec7d

vinta-derive-skills git:20261103.6a7c968A

Immutable. This exact content never changes and is served at /api/v1/blob/e3b7fe997829ec7d.

---
name: vinta-derive-skills
description: Author project-specific skills under `ai-tools/skills/`. Always copies the project-agnostic foundation set (`plan-feature`, `create-spec`, `open-pr-from-context`) verbatim from this skill's bundled resources; ships `create-qa-use-cases` and keeps `plan-feature`'s e2e sections only when `add-e2e-test` is enabled. Generates `implement-plan` from a parameterized template using project-specific commands + branch / commit / PR conventions captured in the bootstrap interview. Asks the user whether `add-e2e-test` and `add-env-var` are needed (optional foundation skills — only ship if applicable). Stack-specific skills (Medplum, Django, etc) come from user-supplied templates pointed at by the orchestrator — see [bootstrap-ai-tools/resources/stacks/<stack>/notes.md](../vinta-bootstrap-ai-tools/resources/stacks/) for the categories per stack.
---

# Derive skills

Author the project's `ai-tools/skills/<name>/SKILL.md` files. Skills are reusable instruction sets the agent invokes when a task matches their description.

## Output

`ai-tools/skills/<name>/SKILL.md` for each skill. Some skills bring resources (templates, scripts) — those go under `ai-tools/skills/<name>/resources/`.

## Inputs

1. Inventory from [vinta-analyze-codebase](../vinta-analyze-codebase/SKILL.md), specifically `existing_ai_artifacts.skills` — every skill file already in the repo with name, description, classification (`vinta-managed` / `foundation-shape` / `project-custom`).
2. AGENTS.md from [vinta-write-agents-md](../vinta-write-agents-md/SKILL.md).
3. Step 0 interview answers from [vinta-bootstrap-ai-tools](../vinta-bootstrap-ai-tools/SKILL.md), including the [Existing AI artifacts](../vinta-bootstrap-ai-tools/SKILL.md#e-existing-ai-artifacts-per-artifact-disposition) group's **per-skill disposition** (migrate / keep / drop / replace).
4. Stack matches from the inventory; for each, the skill categories listed in [bootstrap-ai-tools/resources/stacks/<stack>/notes.md](../vinta-bootstrap-ai-tools/resources/stacks/).
5. User-supplied stack templates for matched stacks (path / URL / package — asked at runtime).

## Reconcile against existing skills (do this FIRST)

Before drafting any new SKILL.md, walk through every entry in `existing_ai_artifacts.skills` and apply the disposition the user picked in the bootstrap interview's [Existing AI artifacts](../vinta-bootstrap-ai-tools/SKILL.md#e-existing-ai-artifacts-per-artifact-disposition) group:

- **Migrate to `ai-tools/skills/<name>/`** — `git mv` the existing skill folder (with all its resources) into the canonical layout. After move, scan the body for hard-coded vendor paths that no longer apply (`.cursor/skills/...` self-references, etc.) and rewrite to the new path. `setup-ai-tools.mjs` will re-link to the chosen vendors.
- **Keep in current vendor path, don't touch** — leave it where it is; AGENTS.md may reference it; downstream skill setup won't manage it. **Don't ship a foundation duplicate that would shadow it.**
- **Drop** — log removal; don't emit anything.
- **Replace with Vinta foundation version** (foundation-shape only — `plan-feature`, `create-spec`, `create-qa-use-cases`, `implement-plan`, `add-e2e-test`, `add-env-var`, `add-one-off-script`, `prepare-worktree`, `thermo-nuclear-code-quality-review`, `interview-ui`, `thermo-nuclear-review-loop`, `deslop-comments`, `handoff`, `handoff-to-client`, `write-unit-test`) — proceed with the bucket A / B / C flow below for that name. The user has explicitly opted into overwriting their version.
- **`vinta-managed`** (any skill whose dir starts with `vinta-`) — leave alone. These come from the `vinta-ai-workflows` CLI; `vinta-derive-skills` doesn't manage them.

For each name in the foundation set: only emit it (verbatim copy / generate from template / interview-draft) when no existing skill of that name is being migrated or kept. If the user said `Migrate` or `Keep` for an existing `plan-feature/SKILL.md`, **do not overwrite**.

For project-custom skills: never auto-generate something the user already has under a different name. If the user has `<their-name>/SKILL.md` covering ground a stack template would also cover, ask (`AskUserQuestion`) before adding the stack-template version.

## Foundation set

Three buckets:

### A. Always copy — verbatim from bundled resources

These are project-agnostic enough to ship as-is. Copy from this skill's [resources/foundation-skills/](resources/foundation-skills/) into the target's `ai-tools/skills/<name>/SKILL.md`.

| Skill | Source | What it does |
|---|---|---|
| `plan-feature` | [resources/foundation-skills/plan-feature/SKILL.md](resources/foundation-skills/plan-feature/SKILL.md) | Author phased implementation plans for a new feature, with interview-driven scoping. |
| `create-spec` | [resources/foundation-skills/create-spec/SKILL.md](resources/foundation-skills/create-spec/SKILL.md) | Turn a raw feature prompt into a structured spec doc. |
| `create-qa-use-cases` | [resources/foundation-skills/create-qa-use-cases/SKILL.md](resources/foundation-skills/create-qa-use-cases/SKILL.md) | Bootstrap a project's `QA_USE_CASES.md` from the active spec/plan. **E2E-gated** — ship only when `foundation_skills.add-e2e-test` is `enabled`; QA use-cases exist to seed e2e specs, so a no-e2e project has no use for it. |
| `open-pr-from-context` | [resources/foundation-skills/open-pr-from-context/](resources/foundation-skills/open-pr-from-context/) | Thin SKILL.md wrapper around [scripts/open-pr.sh](resources/foundation-skills/open-pr-from-context/scripts/open-pr.sh) — bash script that publishes one `.vinta-ai-workflows/prs-context/{feature}/{phase}.md` file as a real PR + inline comments via the project's PR CLI (`gh` / `glab`). Used by `implement-plan` (when CLI + `yq`/`jq` are available) and standalone (to publish a `pending` file later). |
| `deslop-comments` | [resources/foundation-skills/deslop-comments/SKILL.md](resources/foundation-skills/deslop-comments/SKILL.md) | Rewrite comments + doc blocks touched during a task into Simple English — strip AI-slop vocabulary and negative framing; comment-only, no renames or behavior change. **Always ships** — `integrate-phase` / `amend-plan` run it over the PR-context file, and maestro's `deslop` chore runs it on every phase after the review approves, so it must exist in the target. Also user-invokable standalone ("deslop these comments"). |
| `thermo-nuclear-review-loop` | [resources/foundation-skills/thermo-nuclear-review-loop/SKILL.md](resources/foundation-skills/thermo-nuclear-review-loop/SKILL.md) | Review-and-fix loop over a change: the running agent spawns one reviewer sub-agent, verifies its findings, fixes the justified ones and answers the rest, until the reviewer explicitly approves under a strict code-quality standard. Scope questions the reviewer raises go to the human rather than being assumed. **Always ships** — it is the per-phase review on both execution paths: `review-phase` runs it over every phase with the conductor as the loop's host, and every `vinta-ai-maestro` workflow `plan-feature` writes names it in its `review` chore, which runs on the implementer's session once a phase's gates are green. Also user-invokable standalone ("thermo-nuclear review loop"). |
| `handoff` | [resources/foundation-skills/handoff/SKILL.md](resources/foundation-skills/handoff/SKILL.md) | Write or consume a session-continuation handoff doc under `.vinta-ai-workflows/handoffs/` — goal, current state, decisions + rationale, landmines, next step — so a fresh agent session (or a teammate) resumes in-flight work without re-deriving context. **Always ships** — fully project-agnostic; standalone (no cross-links into the always-on trio). |
| `prepare-worktree` | [resources/foundation-skills/prepare-worktree/](resources/foundation-skills/prepare-worktree/) | Provision a runnable, isolated git worktree for parallel feature work — give every worktree its own copy of the dep dirs (copy-on-write clone, or reinstall for virtualenvs / yarn PnP; never a symlink), copy + mutate env files, fork dev + test DBs when migrations are in scope, namespace docker-compose project + network, OS-sandbox the worktree so main-checkout writes are blocked (bundled [scripts/sandbox-run.sh](resources/foundation-skills/prepare-worktree/scripts/sandbox-run.sh) — `sandbox-exec` / `bwrap`), write a teardown-ready summary YAML. Used by `implement-plan` when Step 0 question (c) opts in; standalone for ad-hoc isolation. Opt-in (bucket A + ask-first via the bootstrap's **Optional foundation skills** interview). |
| `thermo-nuclear-code-quality-review` | [resources/foundation-skills/thermo-nuclear-code-quality-review/SKILL.md](resources/foundation-skills/thermo-nuclear-code-quality-review/SKILL.md) | Opt-in deep structural-maintainability audit of a diff — abstraction quality, giant files, spaghetti-condition growth — harsher than the standard per-phase review; hunts for "code-judo" reframes that collapse branches / helpers / layers rather than polishing them. Read-only; hands each fix to the `fixer` agent. Invoked deliberately by the user; the per-phase review loop already carries a structural-simplification lens, and this is the deeper audit on top of it. Opt-in (bucket A + ask-first via the bootstrap's **Optional foundation skills** interview). |
| `interview-ui` | [resources/foundation-skills/interview-ui/](resources/foundation-skills/interview-ui/) | Run a decision-heavy interview as a full-screen browser form — one decision per screen with context, Markdown/Mermaid explainers, option cards with consequences, and an in-page question panel for the agent (questions, not decisions). The server and the page are `vinta-ai-maestro interview` (`start` / `wait` / `status` / `stop`), rendered with maestro's design system; rounds and answers are JSON under `.vinta-ai-workflows/interviews/<slug>/`. `create-spec` offers it at the start of Step 0 when the skill is present. **Copy the whole directory** (SKILL.md + `resources/examples/`). Opt-in (bucket A + ask-first via the bootstrap's **Optional foundation skills** interview). |

These reference each other:

- `plan-feature` reads spec; dispatches `create-qa-use-cases` when the doc is missing (e2e-enabled projects only — see below).
- `implement-plan` (bucket B below) writes per-phase PR-context files following [resources/prs-context-template.md](resources/prs-context-template.md), then invokes `open-pr-from-context` when a PR CLI is detected.

The **always-on unit** is `plan-feature` + `create-spec` + `open-pr-from-context` — copy all three or none. `deslop-comments` also always ships (independently — it doesn't cross-reference the trio), because `integrate-phase` / `amend-plan` run it over the PR-context file and maestro's `deslop` chore names it; a target missing it would leave those steps dangling. `thermo-nuclear-review-loop` also always ships independently, because `review-phase` runs it over every phase and the `review` chore of every workflow `plan-feature` emits names it; a target missing it would leave every phase, on either execution path, with a review it cannot perform. `handoff` also always ships independently — its body is fully project-agnostic and nothing else depends on it. `create-qa-use-cases` joins the unit **only when `foundation_skills.add-e2e-test` is `enabled`**; when add-e2e-test is disabled (or absent), don't ship it and set `foundation_skills.create-qa-use-cases: disabled` in `.vinta-ai-workflows.yaml` to match.

**E2E stripping in `plan-feature`.** `plan-feature` ships verbatim but carries e2e-only regions delimited by `<!-- e2e:start -->` / `<!-- e2e:end -->` lines. When copying it, run an e2e pass:

- **Always delete the marker lines themselves** (`<!-- e2e:start -->` / `<!-- e2e:end -->`) so they never reach the shipped file.
- **When `foundation_skills.add-e2e-test` is `disabled` or absent**, also delete everything *between* each marker pair. Result: a no-e2e project gets a `plan-feature` with zero Playwright / `QA_USE_CASES.md` / `pr-screenshots/` / `add-e2e-test` references — no noise in the plans it authors.
- **When `add-e2e-test` is `enabled`**, keep the enclosed content (strip only the markers).

After copying, scan each for project-specific path references that no longer match the target (the bundled copies may still carry source-repo paths such as `<source-repo>/ai-plans/` or `apps/<service>/`). Replace with the target's paths from the inventory.

### B. Generate — the **plan-execution unit** + `systematic-debugging` + `write-unit-test` from templates

Project-specific skills, generated from templates because their bodies cite real test commands, branch conventions, agent dispatch table, PR / co-author policy, or selected MCP tools.

The forward-execution + amend flow is decomposed into a **plan-execution unit** — two thin conductors that dispatch to three shared single-purpose sub-skills. All five render from [resources/plan-execution/shell/](resources/plan-execution/shell/), which pulls shared bodies from [resources/plan-execution/partials/](resources/plan-execution/partials/) (see [Rendering the plan-execution unit](#rendering-the-plan-execution-unit) below):

- **`implement-plan`** — thin conductor: parse plan → classify phases → resolve one `WORKROOT` → per-phase loop (dispatch to the three sub-skills) → track → report. From `shell/implement-plan-template.md`.
- **`amend-plan`** — thin conductor: history rewriting (revise plan, amend commits, force-push, rebase stacked downstreams, refresh PR-context). From `shell/amend-plan-template.md`. Reuses `implement-phase` (for `amend-existing` rewrites) + `review-phase` (verbatim), keeps its own git topology.
- **`implement-phase`** — compose implementer prompt + pick model + spawn one implementer subagent. From `shell/implement-phase-template.md`.
- **`review-phase`** — the thermo-nuclear review loop over one phase, with the conductor as the loop's host: one reviewer sub-agent one tier above the phase's implementer, the implementer fixing, questions only a person can settle put to the human. From `shell/review-phase-template.md`, which includes `partials/review-loop.md#LOOP`. **Shared by all three conductors** (implement-plan, amend-plan, systematic-debugging).
- **`integrate-phase`** — push the reviewed phase + open PR via context file. From `shell/integrate-phase-template.md`, rendered **commit-strategy-resolved** (see the [commit-strategy substitution table](#commit_strategy_-substitution-table)).

The five are a **co-shipped unit**: `implement-plan` is always generated, so `implement-phase` / `review-phase` / `integrate-phase` are always generated alongside it (they are its decomposition, not independently opt-in — there is no `foundation_skills` enum entry for them, and the bootstrap interview never asks about them). `amend-plan` is likewise always generated. They are separate `ai-tools/skills/<name>/SKILL.md` files so each can be reviewed and evolved on its own.

- **`systematic-debugging`** — root-cause-first debugging flow with project-specific reproduction commands and enforced observability MCP evidence-gathering. From [resources/systematic-debugging-template.md](resources/systematic-debugging-template.md) plus the **MCP-agnostic** evidence-categories block at [resources/systematic-debugging-mcp-tools.md](resources/systematic-debugging-mcp-tools.md) — the rendered SKILL.md instructs the agent to list available MCP tools at runtime and map them to evidence categories (error tracking, traces, logs, metrics, alerts, deploys, dashboards), instead of hardcoding tool names that go stale. **Opt-in** — only generated when `foundation_skills.systematic-debugging` is `enabled` in `.vinta-ai-workflows.yaml`. The free-form list of MCP server identifiers rendered into the body comes from `skills.systematic-debugging.observability_mcp_servers` in the same config. It reuses `review-phase` for its Phase 4 review gate (an `invoke [review-phase]` reference — always resolvable because the plan-execution unit is always shipped).

- **`handoff-to-client`** — generate a markdown API-change handoff document for the client teams consuming an API-only repo: endpoints/operations added / changed / deprecated / removed on the current branch vs the default branch, with request/response shapes, breaking-change flags, and per-platform migration notes. From [resources/handoff-to-client-template.md](resources/handoff-to-client-template.md). **Opt-in** — only generated when `foundation_skills.handoff-to-client` is `enabled` in `.vinta-ai-workflows.yaml` (the bootstrap interview asks only when the repo looks API-only with separate client codebases). The rendered body bakes in `skills.handoff-to-client.*` config: client platforms, API style, optional API spec path, output dir. Standalone — no cross-links into the plan-execution unit.

- **`write-unit-test`** — write durable unit tests for a unit of behavior, enforcing the universal rules (mock only genuine externals, assert the full expected output not counts/truthiness, clean up created data or roll back the transaction, never hit an external service unavailable in dev/test, keep test logic literal, decouple from internals) plus this project's captured test conventions. From [resources/write-unit-test-template.md](resources/write-unit-test-template.md). **Default-on, not ask-first** — generated whenever `foundation_skills.write-unit-test` is `enabled`, which bootstrap sets automatically when `vinta-analyze-codebase` detected a unit-test framework (`disabled` → skip; nothing to render against). The rendered body bakes in `skills.write-unit-test.*` config: test style, data setup, assertion style, DB isolation, plus any inferred `additional_conventions`. Standalone — no cross-links into the plan-execution unit.

  **Ship the packs too — two axes, matched independently.** Alongside the rendered `SKILL.md`, copy the **verbatim** packs the project needs from [resources/write-unit-test-packs/](resources/write-unit-test-packs/) into `ai-tools/skills/write-unit-test/resources/packs/`. There are two kinds, selected on **different** signals so framework-specific advice only ships when its framework is actually present:

  - **Runner pack** (`resources/packs/runners/<runner>.md` → shipped to `.../packs/runners/`) — the test-runner idioms (structure, assertions, mocking). Select **one (or, in a polyglot repo, one per language)** by `inventory.tests.unit_framework`: `pytest` → `pytest.md`; `vitest` → `vitest.md`; `jest` → `jest.md`. Runner packs are **stack-agnostic** — they contain no Django/FastAPI/Medplum specifics.
  - **Stack pack(s)** (`resources/packs/stacks/<stack>.md` → shipped to `.../packs/stacks/`) — DB isolation, framework client/test-client, domain mocks. Ship **every** pack whose stack the inventory matched (a project can match several — e.g. `nextjs` + `react`, or `tanstack-start` + `react`). Match on `inventory.frameworks` / `inventory.stacks`:

    | Detected signal | Stack pack |
    |---|---|
    | Django | `django.md` |
    | FastAPI | `fastapi.md` |
    | Flask | `flask.md` |
    | Medplum (`@medplum/*`) | `medplum.md` |
    | React (renders UI) | `react.md` |
    | Next.js | `nextjs.md` |
    | TanStack Start (`@tanstack/react-start`) | `tanstack-start.md` |
    | React Router (`react-router` / `@react-router/*`) | `react-router.md` |
    | Prisma (`@prisma/client`) | `prisma.md` |
    | Standalone Python package (no web framework) | `python-package.md` |
    | Standalone TS package / lib (no framework) | `typescript-package.md` |

  **Only ship what matched — never dump the whole directory.** A FastAPI project must not receive `django.md`; a pure-library project gets its `*-package.md` and **no** web-framework pack. When no stack pack matches (e.g. a plain script repo), ship the runner pack alone — the rendered body already falls back to the universal rules + the nearest existing tests. When the runner or a clearly-present stack has no pack here, ship nothing for it and record the gap in the orchestrator's final summary (a candidate for a new pack via the source repo's `add-foundation-skill` flow). The `{{FRAMEWORK_PACK_LOADER_BLOCK}}` + `{{FRAMEWORK_PACKS_LIST}}` placeholders are rendered from exactly the set of packs you shipped.

  **Some stack packs need a one-time project-setup step, done here — not per test.** A pack may require a project-level test-setup fixture that must exist before any test runs; wire it once at derive time so no individual test has to. Currently:

  - **Medplum** (Vitest) — `MockClient` search filtering (`searchResources` / `searchOne` with filters) silently returns nothing unless the FHIR structure definitions + search parameters are indexed in the test process. When you ship `medplum.md`, also install the **two** bundled Vitest setup files and wire both hooks:
    - Copy [resources/write-unit-test-packs/stacks/medplum-test.globalSetup.ts](resources/write-unit-test-packs/stacks/medplum-test.globalSetup.ts) → the project's setup dir (commonly `src/test.globalSetup.ts`) and register it in `vitest.config.*` under `test.globalSetup`. It indexes the bundles once in the main process and shares `globalSchema.types` to every worker via `project.provide`.
    - Copy [resources/write-unit-test-packs/stacks/medplum-test.setup.ts](resources/write-unit-test-packs/stacks/medplum-test.setup.ts) → `src/test.setup.ts` and register it under `test.setupFiles`. It `inject`s the shared index and assigns it at **module scope** (not `beforeAll` — timing matters).
    - **Deps:** `@medplum/definitions` as a devDependency; Vitest **3+** (the `provide`/`inject` API); Node **≥ 22.4** or add `ws` for the WebSocket polyfill (@medplum/core v5). Flag any of these that are missing.
    - **Version alignment:** `@medplum/core`, `@medplum/definitions`, `@medplum/mock`, `@medplum/fhirtypes` must be on matching versions.
    - **Idempotent + merge, don't clobber:** if the project already has `test.setup.ts` / `test.globalSetup.ts`, merge the indexing + inject/provide logic in rather than overwriting; if it already does this, do nothing. Create the files + point the runner config at them only when absent.
    - **Jest-based Medplum project** (rare — the catalog is Vitest-first): there's no bundled Jest variant; index the FHIR bundles in a single `setupFilesAfterEnv` file instead and record it as a follow-up to add a Jest snippet to the catalogue.

    This is a project mutation — do it once at bootstrap. Canonical source is the team's snippet catalogue (`vintasoftware/medplum-snippet-catalog`, `TestSetup/`); the bundled copies (dated in their headers) keep bootstrap offline-safe — prefer the live catalogue if it has advanced. The rendered `medplum.md` only *mentions* the override so tests don't re-index.

Every plan-execution shell + `systematic-debugging` consumes the placeholder set below — render each by substituting from the inventory + Step 0 interview answers. `systematic-debugging` reuses the same set plus two extra placeholders (`{{OBSERVABILITY_MCP_BLOCK}}`, `{{OBSERVABILITY_MCP_LIST}}`) rendered from the catalogue.

#### Rendering the plan-execution unit

Shells under `plan-execution/shell/` are thin — most content lives in `plan-execution/partials/` and is spliced in at render time. Per shell, in order:

1. **Expand includes.** Replace every include directive with the referenced partial content:
   - `<!-- include: partials/<file>.md -->` — the whole partial body.
   - `<!-- include: partials/<file>.md#BLOCK -->` — only the block between `<!-- block-begin: BLOCK -->` / `<!-- block-end: BLOCK -->` (markers excluded). Includes may nest (e.g. `implementer-prompt.md#FULL` itself includes `#INNER_OUTER_LOOP`) — expand recursively.
   - `<!-- include: partials/commit-strategy/<RESOLVED>.md#BLOCK -->` — bind `<RESOLVED>` to the project's `policies.commit_strategy` (`stacked` → `stacked.md`, `modular` → `modular.md`). For `ask`, see the split below.
   - Ignore the `rendering-guidance` appendix at the bottom of `commit-strategy/modular.md` — it is derive-time reference for the `{{MODULAR_*}}` placeholders, not shippable content.
2. **Substitute `{{PLACEHOLDER}}`** using the table below (+ the commit-strategy table).
3. **Write** the fully-expanded, fully-substituted body to `ai-tools/skills/<name>/SKILL.md`. No `<!-- include -->` marker and no `{{...}}` may survive in the shipped file.

**The `WORKROOT` seam.** The conductor resolves `WORKROOT` / `BASE_BRANCH` / `SANDBOX_TIER` once per lane (`partials/worktree-seam.md#WORKROOT_RESOLUTION`) and every sub-skill uses them as data — this is why the rendered skills carry no scattered `if use_worktree` branches. `<WORKROOT>` / `<BASE_BRANCH>` / `<main_checkout>` / `<pool_root>` / `<phase.base_branch>` / `<RESOLVED>` are **angle-bracket runtime slots the agent fills at execution time**, not `{{...}}` derive-time placeholders — leave them verbatim in the shipped file.

**The parallel-lanes seam.** `partials/parallel-lanes.md` carries the DAG scheduler: the graph parse (`DAG_PARSE`), the worktree pool (`LANE_WORKTREE_POOL`), the dependency-derived branch topology (`LANE_TOPOLOGY`), the dispatch loop (`LANE_SCHEDULER`), the tracking-directory schema (`TRACKING_DIR`), and the sibling-lane write guard (`SIBLING_LANE_ISOLATION`, included by `worktree-seam.md#STRAY_WRITE_CHECK`). All six blocks are project-agnostic and carry no new `{{...}}` placeholders of their own beyond ones the shells already substitute (`{{PLAN_DIR}}`, `{{BUILD_CMD}}`, `{{E2E_RUN_OPTION_TRACKING}}`, `{{TRACKING_BRANCH_FIELD}}`, `{{TRACKING_PHASE_BRANCH_FIELD}}`).

**The needs-input seam.** A dispatched agent cannot call the harness's question tool, so it returns its questions as data. `implementer-prompt.md#NEEDS_INPUT` is prompt text (spliced into `FULL` and into `amend-plan` 4b) telling the phase agent to stop and return `status: NEEDS_INPUT` with a `questions:` block. `partials/relay-questions.md#RELAY` is included by `implement-phase`, `review-phase` and `amend-plan`, and turns that block into an `AskUserQuestion` call, records the answer, and continues the agent. The block's shape is the **Asking the human** section of the project's `AGENTS.md` ([source](../vinta-write-agents-md/resources/asking-the-human.md)). Both blocks are project-agnostic and take no `{{...}}` placeholders.

**The dispatched-agent seam.** `partials/dispatched-agent.md` carries the one rule that spans both directions of the unit: the agent doing a phase never dispatches. `CONDUCTOR_ENTRY_GUARD` goes near the top of all four conductors (`implement-plan`, `implement-phase`, `review-phase`, `amend-plan`) and stops an agent that was itself handed one phase — by another conductor, or by [vinta-ai-maestro](https://github.com/vintasoftware/vinta-ai-workflows/tree/main/packages/vinta-ai-maestro) — from re-entering the orchestrator one level down. `NO_NESTED_DISPATCH` is prompt text, spliced into `implementer-prompt.md#INNER_OUTER_LOOP`, so every composed implementer prompt (forward and amend) carries it. Both blocks are project-agnostic and take no `{{...}}` placeholders.

**When worktrees are unavailable** — `foundation_skills.prepare-worktree` is `disabled` **and** `commands.worktree_prepare` is unset — parallel execution is impossible (several agents cannot share one working tree). Render the plan-execution unit sequential-only: strip the Step 0 (e) question and the `{If run_options.parallel_phases = true:}` gate from the implementer prompt, and keep the `parallel-lanes` blocks — the scheduler with `max_parallel_lanes = 1` **is** the sequential path, and the DAG still orders the phases correctly. Only the pool provisioning collapses to the single-worktree branch of `WORKROOT_RESOLUTION`.

**The review-canvas gate stays, whatever the config says.** The PR-context block carries a `{If integrations.pr-review-canvas = enabled in .vinta-ai-workflows.yaml:}` paragraph. Render it verbatim, including the gate. It is a runtime gate that the integrating agent reads, so a team that enables the integration later through [vinta-sync-ai-tools](../vinta-sync-ai-tools/SKILL.md) needs no re-derive. Strip it only when `policies.pr_creation = branches-only`, because then no agent ever runs `open-pr.sh`.

**`ask` → two `integrate-phase` variants, no dual-render.** When `policies.commit_strategy = ask`, render `integrate-phase-template.md` **twice**:
- `ai-tools/skills/integrate-phase-stacked/SKILL.md` — `<RESOLVED>` = `stacked`, `{{INTEGRATE_PHASE_NAME}}` = `integrate-phase-stacked`.
- `ai-tools/skills/integrate-phase-modular/SKILL.md` — `<RESOLVED>` = `modular`, `{{INTEGRATE_PHASE_NAME}}` = `integrate-phase-modular`.

The conductor's `{{INTEGRATE_PHASE_DISPATCH}}` then renders as a one-line runtime dispatch on `run_options.commit_strategy_resolved` (see the commit-strategy table). For `stacked` / `modular` projects, `integrate-phase-template.md` renders **once** to `ai-tools/skills/integrate-phase/` with a single topology body and no runtime branch.

The remaining per-shell placeholders:

| Placeholder | Source | Example |
|---|---|---|
| `{{PROJECT_NAME}}` | inventory.repo.name | `ACME Sleep` |
| `{{STACK_SUMMARY}}` | inventory.frameworks one-liner | `pnpm + Turbo + Vite + React + Medplum + Playwright` |
| `{{PLAN_DIR}}` | derived from where `*_IMPLEMENTATION_PLAN.md` lives | `ai-plans` or `apps/<service>/ai-plans` |
| `{{LINT_CMD}}` | inventory.commands.lint | `pnpm lint:fix` or `pre-commit run --files <changed>` or `ruff check` |
| `{{FORMAT_CMD}}` | inventory.commands.format | `pnpm format` or `ruff format` |
| `{{BUILD_CMD}}` | inventory.commands.build | `pnpm build` or `make all-tests` (when build is the type gate) |
| `{{TYPECHECK_NOTE}}` | derived | empty, or ` plus full mypy via 'mypy app/'` |
| `{{TEST_CMD}}` | inventory.commands.test_unit | `pnpm test`, `make all-tests`, `pytest` |
| `{{SCOPED_TEST_NOTE}}` | derived | empty, or `; per-app via 'pnpm test:patient' / 'pnpm test:provider'` |
| `{{NEW_TEST_CMD_PATTERN}}` | derived | `pnpm --filter <pkg> test -- <new-test-path>` or `pytest path/to/new_test.py -x` |
| `{{SCOPED_TEST_PATTERN}}` | `commands.test_unit_scoped` when set, else derived | `pnpm test:patient`, `pytest tests/{unit,integration}/{app}/`, or a template such as `pnpm vitest related {changed_files} --run` — render a template verbatim; the implementer prompt says how to fill `{changed_files}` / `{touches}`, and `vinta-ai-maestro` fills them the same way for its scoped gates |
| `{{E2E_BLOCK}}` | populated only if e2e selected — see C below | block describing e2e command + screenshot copy |
| `{{E2E_OUTER_GATE_LINE}}` | depends on e2e selected | The outer-gate e2e step, **wrapped in a `run_options.run_e2e` runtime gate** so e2e is opt-in per run. Render as `   c. **E2E** (opt-in): {If \`run_options.run_e2e = true\`:} when this phase has an e2e spec, run the project's e2e suite for the touched app + copy screenshots into \`pr-screenshots/\`. {Else:} skip e2e this run (default — e2e makes the phase take a lot longer).` Empty when e2e not selected. |
| `{{E2E_REPORT_FIELD}}` | depends on e2e selected | Report hook for e2e, **guarded by `{If run_options.run_e2e = true:}`** so it only applies when the run opted into e2e: `- E2E: specs run + screenshot paths, or "skipped (run_options.run_e2e = false)".` Empty when e2e not selected. |
| `{{E2E_OUTER_GATE_CHECKLIST}}`, `{{UI_E2E_RULE_LINE}}` | depends on e2e selected | populated or empty. `E2E_OUTER_GATE_CHECKLIST`: a trailing checklist clause ` + the e2e suite when \`run_options.run_e2e = true\`` (leading ` + `) so the gate checklist only demands e2e on opted-in runs. `UI_E2E_RULE_LINE`: an important-rules bullet stating e2e is opt-in via `run_options.run_e2e` (default off, makes runs a lot slower) and only runs for phases carrying an e2e spec. |
| `{{E2E_RUN_OPTION_QUESTION}}` | depends on e2e selected | The Step 0 opt-in question **(f)** for `implement-plan`, e2e projects only (empty otherwise). Render as ``\n\n   f. **Run E2E tests this run?** *"Some phases carry Playwright e2e specs. Running them each phase makes the implementation take a LOT longer (browser boot, seeded data, screenshot capture). Run them this time, or skip and rely on unit + integration coverage?"* Options: `Skip e2e (default) — faster`, `Run e2e — slower but exercises the browser flows`. Default = `Skip`. Records `run_options.run_e2e` (`true` only for the `Run e2e` answer).`` (starts with `\n\n`; vanishes cleanly for no-e2e projects) |
| `{{E2E_RUN_OPTION_CONFIRM}}` | depends on e2e selected | e2e projects: `` + `run_options.run_e2e` `` (leading ` + `). Empty otherwise. |
| `{{E2E_RUN_OPTION_TRAILER}}` | depends on e2e selected | e2e projects: ` E2E execution follows `run_options.run_e2e` (opt-in; off by default).` (leading space). Empty otherwise. |
| `{{E2E_RUN_OPTION_TRACKING}}` | depends on e2e selected | e2e projects: `` , `run_e2e` `` (leading `, `) — appends `run_e2e` to a run-options list (the tracking schema line + the implement-phase / review-phase pass-through lists). Empty otherwise. |
| `{{E2E_RUN_OPTION_RULE}}` | depends on e2e selected | e2e projects: ` `run_options.run_e2e` controls whether the e2e suites run in the outer gate + review layers ([Implement](#1a-implement) + [Review](#1b-review)) — off by default because e2e makes each phase a lot slower; on only when the user opts in at Step 0.` (leading space). Empty otherwise. |
| `{{STACK_SPECIFIC_DEPLOY_BLOCK}}` | from matched stacks | `- Bots: \`pnpm bots:build\`, \`pnpm bots:deploy --env=dev-<handle>\`...` for Medplum, or empty |
| `{{CODE_HOST}}` | Step 0 interview | `GitHub`, `GitLab`, `Bitbucket` |
| `{{DEFAULT_BRANCH}}` | inventory.repo.default_branch | `main` or `master` |
| `{{PR_*}}` family (`{{PR_POLICY_DESCRIPTION}}`, `{{PR_POLICY_BLOCK}}`, `{{PR_REMINDER_LINE}}`, `{{BRANCH_PUSH_HEADING}}`, `{{PR_LINK_NOTE}}`, `{{FINAL_REPORT_PR_NOTE}}`, `{{PR_RULE_TAIL}}`, `{{PR_CHECKLIST_NOTE}}`, `{{FINAL_CHECKLIST_PR_NOTE}}`, `{{PUSH_INSTRUCTION_LINE}}`) | Step 0 (PR creation policy) | Describes whether agents create PRs vs only push branches. **Note:** PR creation itself goes through the rendered skill's **Open PR via context file** step (`prs-context` file + bundled `open-pr.sh`), not raw `gh pr create` lines. These placeholders cover the framing (description, push instructions, checklist + summary phrasing) — the **Open PR via context file** matrix consumes the policy directly. |
| `{{COAUTHOR_*}}` family (`{{COAUTHOR_POLICY_BLOCK}}`, `{{COAUTHOR_INSTRUCTION_LINE}}`, `{{COAUTHOR_RULE_LINE}}`, `{{COAUTHOR_CHECKLIST_NOTE}}`) | Step 0 (AI co-author policy) | If "forbidden": "Do NOT add `Co-Authored-By: Claude` ..." everywhere. If "allowed": empty / softer language. |
| `{{COMMIT_STYLE_LINE}}` | Step 0 (commit style) + repo log | `Default subject: short imperative, ≤72 chars` or `Conventional Commits format: type(scope): subject` |
| `{{ANTI_GIT_ADD_ALL_REASON}}` | derived from common artifacts in target | `secrets, .env files, build artifacts, .auth/ live in the tree` |
| `{{STAGE_PATTERN}}` | derived from monorepo shape | `apps/... lib/... e2e/... ai-plans/...` or `<app>/... tests/... ai-plans/...` |
| `{{PROJECT_SKILLS_LIST}}` | computed from skills emitted | comma-separated names of the skills the project actually has |
| `{{AGENT_DISPATCH_TABLE}}` | from [vinta-derive-subagents](../vinta-derive-subagents/SKILL.md) output | markdown table mapping phase shapes → agent type. Rendered into `implement-phase`. |
| `{{INTEGRATE_PHASE_NAME}}` | derived from `policies.commit_strategy` | The integrate-phase skill's own `name:` + dir. `integrate-phase` for `stacked` / `modular`; `integrate-phase-stacked` / `integrate-phase-modular` for the two `ask` renders. |
| `{{INTEGRATE_PHASE_DISPATCH}}` | derived from `policies.commit_strategy` | How the `implement-plan` conductor names the integrate step. `stacked` / `modular`: `` [integrate-phase](../integrate-phase/SKILL.md) ``. `ask`: `` the resolved integrate-phase variant — [integrate-phase-stacked](../integrate-phase-stacked/SKILL.md) when `run_options.commit_strategy_resolved = stacked-branches`, else [integrate-phase-modular](../integrate-phase-modular/SKILL.md) ``. |
| `{{INTEGRATE_PHASE_LINK}}` | derived from `policies.commit_strategy` | Path used by `amend-plan`'s PR-template cross-reference. `../integrate-phase/SKILL.md` for `stacked` / `modular`; `../integrate-phase-stacked/SKILL.md` for `ask` (amend-plan only runs under the stacked topology). |
| `{{OBSERVABILITY_MCP_BLOCK}}` | rendered from [resources/systematic-debugging-mcp-tools.md](resources/systematic-debugging-mcp-tools.md) | the verbatim "Phase 0 evidence categories" block — same content for every project. When `skills.systematic-debugging.observability_mcp_servers` is empty, renders the no-tools fallback paragraph instead. The block is MCP-agnostic on purpose: tool names are discovered at runtime, not baked at generation time. |
| `{{OBSERVABILITY_MCP_LIST}}` | derived from `skills.systematic-debugging.observability_mcp_servers` | comma-separated MCP server identifiers as the user named them at bootstrap (e.g. `sentry, datadog, our-internal-traces`). Renders `none configured` when empty. |
| `{{API_STYLE}}` | `skills.handoff-to-client.api_style` (handoff-to-client only) | `REST`, `GraphQL`, `gRPC`, `REST + webhooks` |
| `{{CLIENT_PLATFORMS_LIST}}` | `skills.handoff-to-client.client_platforms` (handoff-to-client only) | comma-separated, e.g. `iOS (Swift), Android (Kotlin), web SPA (React)` |
| `{{CLIENT_HANDOFF_DIR}}` | `skills.handoff-to-client.output_dir` (handoff-to-client only) | `.vinta-ai-workflows/client-handoffs` (default) |
| `{{API_SPEC_BLOCK}}` | derived from `skills.handoff-to-client.api_spec_path` (handoff-to-client only) | when set: a paragraph instructing the agent to regenerate/read the spec at that path on both base and branch and diff the two as the primary change-enumeration source (cross-checking against the code diff). When absent: a one-line note that no machine-readable spec exists, so shapes come from route/serializer code alone. |
| `{{FRAMEWORK_PACKS_LIST}}` | derived from the packs shipped (write-unit-test only) | comma-separated names of the runner + stack packs that landed under `resources/packs/`, e.g. `pytest + Django` or `vitest, React, TanStack Start`. Renders `the project's test framework` when only the runner shipped with no stack pack. |
| `{{FRAMEWORK_PACK_LOADER_BLOCK}}` | derived from the packs shipped (write-unit-test only) | bullets pointing at each shipped pack, runner first then stacks: `- Runner: **pytest** → read [resources/packs/runners/pytest.md](resources/packs/runners/pytest.md).` then `- Stack: **Django** → read [resources/packs/stacks/django.md](resources/packs/stacks/django.md).` (one bullet per matched stack). When only the runner shipped, emit just its bullet plus `- No stack pack matched — apply the universal rules and mirror the nearest existing tests.` When no pack shipped at all, render that single fallback line alone. |
| `{{PROJECT_TEST_PREFERENCES_BLOCK}}` | derived from `skills.write-unit-test.{test_style,data_setup,assertion_style,db_isolation}` (write-unit-test only) | one bullet per field whose value is **not** `framework-default`, phrased as an instruction: `test_style: classes` → `- Group tests in test classes (not standalone functions).`; `data_setup: factories` → `- Build test data with factory classes / builders, not inline literals or fixture files.`; `assertion_style: plain-assert` → `- Use plain \`assert\` (not \`self.assertEqual\`-family methods).`; `db_isolation: transaction-rollback` → `- Isolate DB state with transaction rollback (each test in a rolled-back transaction).`. Emit `- No project-specific overrides — follow the framework pack's defaults.` when all four are `framework-default`. |
| `{{ADDITIONAL_CONVENTIONS_BLOCK}}` | derived from `skills.write-unit-test.additional_conventions[]` (write-unit-test only) | when non-empty: a `### Additional project conventions` sub-block listing each entry as a bullet, verbatim. Empty string (whole block omitted) when the array is empty. Starts with a leading blank line so it slots cleanly under the preferences block. |
| `{{DEPENDENCY_LICENSE_BLOCK}}` | derived from `policies.dependency_licenses` | Top-level `## Adding new third-party dependencies` section. Empty string when `enforcement: off` or the block is absent. When `block` / `warn`: short paragraph naming the enforcement mode + the forbidden SPDX list + the pre-install check (`npm view <pkg> license`, PyPI metadata, etc.) + a one-line pointer to the **Dependency licenses** section in the root `AGENTS.md` — linked as `../../../AGENTS.md`, the depth from `ai-tools/skills/<name>/SKILL.md` — for the full table including `allowed_overrides` and `notes`. See [resources/dependency-license-block.md](resources/dependency-license-block.md) for the canonical body. |
| `{{DEPENDENCY_LICENSE_RULE_LINE}}` | derived from `policies.dependency_licenses` | Important-rules bullet. Empty string when `enforcement: off`. When `block`: `- **License check before any new dep.** Refuse \`npm add\` / \`pnpm add\` / \`pip install\` / \`poetry add\` / \`uv add\` / \`cargo add\` / \`go get\` when the package's SPDX license is in the forbidden list — see AGENTS.md **Dependency licenses**. User can grant a one-off override after acknowledging the violation; record the override in \`policies.dependency_licenses.allowed_overrides\` before re-running.` When `warn`: same line but `Proceed but record the violation in the phase report` instead of `Refuse`. |
| `{{COMMIT_STRATEGY_*}}` family (`{{COMMIT_STRATEGY_STEP0_QUESTION}}`, `{{COMMIT_STRATEGY_STEP0_TRAILER}}`, `{{COMMIT_STRATEGY_CONFIRM_NOTE}}`, `{{BRANCH_NAMING_PATTERN_SUMMARY}}`, `{{BRANCH_NAMING_BLOCK}}`, `{{PER_PHASE_COMMIT_BLOCK}}`, `{{PR_OPEN_TIMING_BLOCK}}`, `{{PRS_CONTEXT_FILE_PATH}}`, `{{TRACKING_BRANCH_FIELD}}`, `{{TRACKING_PHASE_BRANCH_FIELD}}`, `{{FINAL_REPORT_BRANCH_SUMMARY}}`, `{{BRANCH_CHECKLIST_LINE}}`, `{{COMMIT_STRATEGY_CHECKLIST_BLOCK}}`, `{{BRANCH_PUSH_HEADING}}`) | Step 0 (`policies.commit_strategy`) | Render based on the project's commit strategy (`stacked-branches` / `modular-commits` / `ask`). Multi-line blocks now come from the commit-strategy partials; see the substitution table immediately after this row. |

### `{{COMMIT_STRATEGY_*}}` substitution table

The multi-line blocks now live in the partials `plan-execution/partials/commit-strategy/{stacked,modular}.md` (marker-delimited blocks). The single-line values are documented in each partial's footer comment. Bind the block/value from the partial matching `policies.commit_strategy`. The `{{MODULAR_EXAMPLE_*}}` + `{{MODULAR_COMMIT_MESSAGE_FORMAT_BLOCK}}` placeholders inside `modular.md`'s `PER_PHASE_COMMIT` block are resolved from that partial's own **rendering-guidance appendix** (driven by `policies.commit_style` — conventional / imperative / other); that appendix is derive-time reference only and is never shipped.

**Where each block lands, and how `ask` is handled:**
- `BRANCH_NAMING` + `PR_OPEN_TIMING` land in **`integrate-phase`**. Under `ask`, `integrate-phase` is rendered **twice** (once per strategy — see [Rendering the plan-execution unit](#rendering-the-plan-execution-unit)); each variant bakes in one strategy's blocks with **no runtime marker**. The conductor dispatches by name via `{{INTEGRATE_PHASE_DISPATCH}}`.
- `PER_PHASE_COMMIT` lands in **`implement-phase`** (the implementer prompt's working instructions). `implement-phase` is not split, so under `ask` it embeds **both** strategy blocks gated by inline runtime markers (`If \`run_options.commit_strategy_resolved = "modular-commits"\`: … Else (\`stacked-branches\`): …`) — same pattern as `pause_between_phases` / `generate_inline_comments`.
- `CHECKLIST` + all single-line values land in the single **`implement-plan`** conductor; under `ask` they render dynamically on `run_options.commit_strategy_resolved` (runtime markers for the block, inline `If … Else …` for single-line values).

| Placeholder | Lands in | `stacked-branches` | `modular-commits` | `ask` |
|---|---|---|---|---|
| `{{COMMIT_STRATEGY_STEP0_QUESTION}}` | implement-plan | empty string | empty string | a third opt-in block analogous to 4a/4b: `\n\n   c. **Commit strategy?** *"This project's commit_strategy is set to ask. Pick one for this run: one branch + one PR per phase (stacked), or one branch + one PR for the whole plan with one atomic commit per logical unit (modular)?"* Options: \`Stacked branches — one branch + PR per phase\`, \`Modular commits — atomic commits, one PR for whole plan\`. Cache answer in tracking under \`run_options.commit_strategy_resolved\`.` (starts with `\n\n`; under stacked / modular the placeholder vanishes without a stray blank line) |
| `{{COMMIT_STRATEGY_STEP0_TRAILER}}` | implement-plan | empty string | empty string | ` Commit-strategy behavior follows \`run_options.commit_strategy_resolved\`.` (leading space) |
| `{{COMMIT_STRATEGY_CONFIRM_NOTE}}` | implement-plan | empty string | empty string | ` + \`run_options.commit_strategy_resolved\`` (leading ` + `) |
| `{{BRANCH_NAMING_PATTERN_SUMMARY}}` | implement-plan | `stacked.md` footer value | `modular.md` footer value | ``branch naming pattern (depends on `run_options.commit_strategy_resolved` — resolved at Step 0)`` |
| `{{BRANCH_NAMING_BLOCK}}` | integrate-phase | `stacked.md#BRANCH_NAMING` | `modular.md#BRANCH_NAMING` | baked per-variant (no runtime marker) |
| `{{PER_PHASE_COMMIT_BLOCK}}` | implement-phase | `stacked.md#PER_PHASE_COMMIT` | `modular.md#PER_PHASE_COMMIT` | both blocks gated by runtime markers |
| `{{PR_OPEN_TIMING_BLOCK}}` | integrate-phase | `stacked.md#PR_OPEN_TIMING` | `modular.md#PR_OPEN_TIMING` | baked per-variant (no runtime marker) |
| `{{PRS_CONTEXT_FILE_PATH}}` | implement-plan + integrate-phase | `stacked.md` footer value | `modular.md` footer value | ``either `.../phase-{phase.id}.md` or `.../plan.md` depending on `run_options.commit_strategy_resolved` `` |
| `{{TRACKING_BRANCH_FIELD}}` | implement-plan | `stacked.md` footer value (empty) | `modular.md` footer value | both gated by runtime markers |
| `{{TRACKING_PHASE_BRANCH_FIELD}}` | implement-plan | `stacked.md` footer value (`, branch, base`) | `modular.md` footer value (empty) | both gated by runtime markers |
| `{{FINAL_REPORT_BRANCH_SUMMARY}}` | implement-plan | `stacked.md` footer value | `modular.md` footer value | dynamic per resolved strategy |
| `{{BRANCH_CHECKLIST_LINE}}` | implement-plan | `stacked.md` footer value | `modular.md` footer value | dynamic per resolved strategy |
| `{{COMMIT_STRATEGY_CHECKLIST_BLOCK}}` | implement-plan | `stacked.md#CHECKLIST` (empty) | `modular.md#CHECKLIST` (3 items) | both variants gated by runtime markers |
| `{{BRANCH_PUSH_HEADING}}` | integrate-phase | `Push stacked branch` | `Push to plan branch` | baked per-variant (`Push stacked branch` / `Push to plan branch`) |

### `{{COMMIT_STRATEGY_REFUSAL_BLOCK}}` (amend-plan only)

`amend-plan-template.md` carries one additional placeholder not used by `implement-plan-template.md`. It renders the "this skill doesn't support this strategy yet" refusal block immediately after the description in the rendered SKILL.md.

| `commit_strategy` | Substitution |
|---|---|
| `stacked-branches` | empty string (current behavior — full amend flow runs) |
| `modular-commits` | a refusal section — see the [refusal-block body](#commit_strategy_refusal_block-body) below |
| `ask` | the same refusal section, but its preamble reads tracking's `run_options.commit_strategy_resolved` first and only refuses when that value resolves to `modular-commits`; when it resolves to `stacked-branches` the section renders an empty string (full amend flow runs) |

#### `{{COMMIT_STRATEGY_REFUSAL_BLOCK}}` body

```markdown

## Unsupported commit strategy

**This skill does not yet support `commit_strategy = modular-commits`.** Detected from `.vinta-ai-workflows.yaml` `policies.commit_strategy` (or `TRACKING_{plan-id}.md` `run_options.commit_strategy_resolved` when the project policy is `ask` and a run is already in flight).

Amending under modular commits requires rewriting an arbitrary number of inline atomic commits across a shared `plan/{plan-id-kebab}` branch. The git topology is fundamentally different from the per-phase stacked branches this skill is designed around — the rewrite plan, force-push targets, and downstream rebase fan-out all differ. Full support is tracked as a follow-up.

**Resolve the amendment one of three ways:**

1. **Append a new phase** — extend the plan with the change as a new `Phase N+1`, then run [implement-plan](../implement-plan/SKILL.md). Cleanest path; preserves the existing commit log.
2. **Hand-craft the amendment** — `git rebase -i plan/{plan-id-kebab}` (or `git commit --fixup` + `git rebase --autosquash`) on the plan branch, force-push, and re-run review manually. Skip this skill entirely.
3. **Re-run the plan from scratch on a new branch** — abandon the in-flight commits (leave them for audit), regenerate the plan with [plan-feature](../plan-feature/SKILL.md), implement forward.

Refuse with this guidance; do not proceed.
```

The commit-strategy block bodies (branch naming, per-phase commit, PR-open timing, checklist) for both strategies live in [resources/plan-execution/partials/commit-strategy/](resources/plan-execution/partials/commit-strategy/) — `stacked.md` + `modular.md`. derive-skills reads the one matching `policies.commit_strategy` (both, under `ask`).

Render each template (expand includes → substitute placeholders → write), producing:

- `ai-tools/skills/implement-plan/SKILL.md` (from `plan-execution/shell/implement-plan-template.md`)
- `ai-tools/skills/implement-phase/SKILL.md` (from `plan-execution/shell/implement-phase-template.md`)
- `ai-tools/skills/review-phase/SKILL.md` (from `plan-execution/shell/review-phase-template.md`)
- `ai-tools/skills/integrate-phase/SKILL.md` (from `plan-execution/shell/integrate-phase-template.md`) — **or**, under `ask`, `ai-tools/skills/integrate-phase-stacked/SKILL.md` + `ai-tools/skills/integrate-phase-modular/SKILL.md` (same template rendered twice)
- `ai-tools/skills/amend-plan/SKILL.md` (from `plan-execution/shell/amend-plan-template.md`)
- `ai-tools/skills/systematic-debugging/SKILL.md` (from `systematic-debugging-template.md`) — only when `foundation_skills.systematic-debugging: enabled`
- `ai-tools/skills/handoff-to-client/SKILL.md` (from `handoff-to-client-template.md`) — only when `foundation_skills.handoff-to-client: enabled`
- `ai-tools/skills/write-unit-test/SKILL.md` (from `write-unit-test-template.md`) **plus** `ai-tools/skills/write-unit-test/resources/packs/runners/<runner>.md` + `.../packs/stacks/<stack>.md` (verbatim from `write-unit-test-packs/`, only the detected runner + matched stacks) — only when `foundation_skills.write-unit-test: enabled` (default-on when a test framework was detected)

The first five are the **plan-execution unit** — render them together (they share partials + placeholders + cross-links); a change to one partial ripples to every shell that includes it.

Validate each output before saving: **no `<!-- include: … -->` marker and no `{{PLACEHOLDER}}` may survive** (angle-bracket runtime slots like `<WORKROOT>` / `<BASE_BRANCH>` / `<RESOLVED-already-bound>` are expected to remain — they are execution-time, not derive-time). If a placeholder survives, the shell/partial needs a substitution rule. Cross-link check: the conductor's links to `implement-phase` / `review-phase` / the integrate variant resolve, and `amend-plan` / `systematic-debugging` → `review-phase` resolve. `systematic-debugging` adds the two MCP-related placeholders; if a project picks an observability tool the catalogue doesn't list, ask the user for a free-form snippet (heading, required calls, fields to extract) and inline it before saving — and consider upstreaming the snippet to [resources/systematic-debugging-mcp-tools.md](resources/systematic-debugging-mcp-tools.md).

### C. Optional — ask the user

Two skills depend on whether the project actually does the thing. Ask via `AskUserQuestion`:

| Skill | Question | If yes |
|---|---|---|
| `add-e2e-test` | "Does this project have e2e tests, or plan to add them?" Options: `Yes — already has them`, `Yes — planning to add`, `No — skip` | Ask if user has an existing template (path / URL). If yes → copy + adapt. If no → draft from scratch using the canonical structure (see "Skill SKILL.md structure" below) interviewing user about: e2e framework, page-object pattern, auth/storage-state pattern, seed-helpers location, tenant scoping in seeds, screenshot conventions for PRs. |
| `add-env-var` | "Does this project have a non-trivial env-var propagation flow (multiple files / build configs / CI to update for one new var)?" Options: `Yes`, `No — single .env file is enough` | Ask if user has an existing template. If yes → copy + adapt. If no → draft from scratch interviewing user about every layer the new var must touch (`.env.example`, build tool envPrefix allowlist, build cache hash inputs, app config module, AGENTS.md env section, CI workflows, deploy-time injection). |
| `add-one-off-script` | "Does this project ever need one-off operational scripts — backfills, cleanups, ad-hoc data fixes?" Options: `Yes — enable`, `No — skip`. Recommend `Yes` for any project with a relational DB; the skill bundles a `BaseOneOffScript` class enforcing dry-run-by-default, idempotent re-runs, batched DB ops, streamed reads, segmented CSV backups (1M cells/file, never nested across tables), interruption-safe signal handlers, and console + filesystem + S3 logging. | Copy [resources/foundation-skills/add-one-off-script/](resources/foundation-skills/add-one-off-script/) into `ai-tools/skills/add-one-off-script/` (SKILL.md + the bundled `resources/one_off_script_base.{py,ts}` templates). Then ask the user whether they want the base class file pre-staged at `<scripts_dir>/one_off/_base.{py,ts}` — default `Yes` for projects whose primary lang is Python or TS. The skill itself prompts to copy the base class on first use if the user said `No` here. |
| `prepare-worktree` | "Do you ever want to run plans / experiments inside an isolated git worktree (separate runnable copy of the app with its own DB, env, compose stack) instead of swapping branches in the main checkout?" Options: `Yes — enable the skill`, `Yes — we provision with our own script/command`, `Yes — generate a provisioning script for this project`, `No — skip`. Recommend `Yes — enable the skill` for projects with long migrations, multi-day plans, or where the user is likely to want parallel concurrent work; recommend the script option instead when the repo already has one (a `bin/*worktree*` / `scripts/*worktree*` file, or a Makefile / `justfile` / `package.json` target with `worktree` in its name). | **Own script/command:** don't copy the skill; set `foundation_skills.prepare-worktree: disabled`. Ask for the command via `AskUserQuestion` (header `Worktree cmd`), offering each candidate found above with its invocation, and take free text for any other. Ask for an optional teardown command the same way (`No teardown command` as an option, with the note that DBs / volumes the prepare command creates would then leak). These land in `commands.worktree_prepare` / `commands.worktree_teardown`. Show the user the `VINTA_WORKTREE_*` contract from the `commands.worktree_prepare` schema description, since their script must honor it. Then ask only the worktree-root follow-up (it lands in `skills.prepare-worktree.worktree_root` — emit `worktree_root` + `summary_dir` and nothing else in that block) and the `use_worktree` default at the end of this cell. **Generate a script:** don't copy the skill; set `foundation_skills.prepare-worktree: disabled`. Ask the worktree-root follow-up first, because the scripts bake it in. Then write the scripts per the bootstrap's [worktree-command guide](../vinta-bootstrap-ai-tools/resources/worktree-command/README.md), which asks for the directory, records `commands.worktree_prepare` / `commands.worktree_teardown`, and verifies the result. Then ask the `use_worktree` follow-up. **Enable the skill:** copy the whole [resources/foundation-skills/prepare-worktree/](resources/foundation-skills/prepare-worktree/) dir into `ai-tools/skills/prepare-worktree/` — both `SKILL.md` and `scripts/sandbox-run.sh` (mark the script executable: `chmod +x ai-tools/skills/prepare-worktree/scripts/sandbox-run.sh`). Then ask three short follow-ups landing in `skills.prepare-worktree.*`: worktree root (default `.claude/worktrees` for claude-code projects, `../<repo>-wt-` sibling dirs otherwise), default deps strategy (`copy` / `reinstall`), default test-DB strategy (`always-fork` / `fork-on-schema-change` / `share`). For either `Yes`, also ask whether `implement-plan` should default its Step 0 question (c) to `yes` — lands in `run_options.implement-plan.use_worktree`. |

If user answers "No" to any: don't ship that skill. Record nothing — the foundation set is just smaller.

If user answers "Yes" but has no template: draft from scratch via interview. The orchestrator can defer this to a separate `vinta-derive-skills` standalone run if the user wants to ship the rest first.

## Stack-specific skills (user-supplied)

[bootstrap-ai-tools/resources/stacks/<stack>/notes.md](../vinta-bootstrap-ai-tools/resources/stacks/) describes **detection signals + skill categories typically needed** for each stack — NOT ready-made content. The team's specific skill library lives wherever they keep it.

For each stack the inventory matched, surface to the user:

> Detected stack `<X>`. The notes for this stack list these skill categories: A, B, C. Do you have existing skill templates for any of these?

`AskUserQuestion`-style prompts:
- *"Do you have a skill template for `<category>`?"* → `Yes — at this path/URL`, `No — record as gap`, `Skip this category for now`.

When the user provides a path:
1. Read the source SKILL.md (or YAML — both formats supported).
2. Replace placeholder paths / dep names / command names with the target's specifics, using the placeholder list from [resources/stacks/<stack>/notes.md](../vinta-bootstrap-ai-tools/resources/stacks/).
3. Drop sections that don't apply.
4. Save to `ai-tools/skills/<name>/SKILL.md`.

When the user has no template: record the category as a gap. Output as part of the orchestrator's final summary.

## Skill SKILL.md structure (for any skill drafted from scratch)

```markdown
---
name: <kebab-case>          # required, matches dir name
description: <text>         # required, dense one-liner of what + when
---

# {Skill name}

Short framing: what this is, why it exists.

## Decision questions (when applicable)
The questions to answer before doing the thing.

## Checklist
Step-by-step, numbered. Each step names files + commands.

## Pitfalls
The "we got burned by this" list.

## Verification
How to confirm the work is done correctly.
```

Length: 100–300 lines. Shorter = under-specified. Longer = should probably split.

## Naming

- Lowercase, kebab-case.
- Verb-led for skills that DO something: `add-bot`, `create-spec`, `manage-access-policy`, `plan-feature`.
- Match the dir name to the `name` field exactly (Cursor + VS Code Copilot constraint).

## Rules

- **Don't ship copy-pasted source-project SKILL.md files unchanged.** Run search-and-replace for source project names / paths / commands before saving.
- **Foundation set is a unit.** Always copy `plan-feature` + `create-spec` + `open-pr-from-context` together — they reference each other. `create-qa-use-cases` joins only when `add-e2e-test` is enabled; when it's not, strip `plan-feature`'s e2e regions (the `<!-- e2e:start/end -->` pass) so no cross-link dangles.
- **The plan-execution unit is generated, not copied.** `implement-plan` + `implement-phase` + `review-phase` + `integrate-phase` + `amend-plan` all have too much project-specific content for verbatim shipping. They are separate files but a **co-shipped unit** — never emit the conductors without the three sub-skills; a conductor whose `[implement-phase]` / `[review-phase]` / integrate link dangles is a broken skill.
- **Optional skills (`add-e2e-test`, `add-env-var`, `add-one-off-script`, `prepare-worktree`, `thermo-nuclear-code-quality-review`, `interview-ui`, `handoff-to-client`) are gated by user answer.** Don't ship them by default. `handoff-to-client` is additionally template-rendered (bucket B) — its config lives under `skills.handoff-to-client.*`.
- **Integration skills are not yours.** `ai-tools/skills/pr-review-canvas/` (and any other skill an `integrations.*` tool installs) is written and refreshed by that tool's CLI. It carries the tool's own marker. Never copy, render, scrub or delete it here. The tool's `doctor` treats any edit as a stale copy.
- **Each skill solves one job.** Two unrelated checklists → split.
- **Reference real files in the target.** Skill links must point to existing paths.
- **Skills auto-load by description.** Specific triggers, specific outcomes.
- **Never use `§N` shorthand to point at sections in any drafted SKILL.md body, agent prompt, or rendered template.** Use the section's full name (and a markdown anchor link when the link target is reachable). `§N` shorthand makes cross-references hard for humans to follow and breaks when section numbering shifts. This rule applies to both the foundation skills shipped verbatim and the template-rendered ones.

## Pitfalls

- **Forgetting to substitute placeholders (or expand includes) in the plan-execution skills.** A surviving `{{TEST_CMD}}` or `<!-- include: … -->` is a runtime confusion. Expand every include, substitute every `{{…}}`, and validate before saving. (Angle-bracket runtime slots like `<WORKROOT>` are meant to stay.)
- **Copying foundation skills and forgetting to scrub source-project paths.** Bundled copies may still carry source-repo paths (e.g. `<source-repo>/ai-plans/`, `apps/<service>/`). Replace with the target's paths.
- **Asking too many questions for optional skills.** If `vinta-analyze-codebase` shows no e2e dir and the user clearly has no e2e setup, it's OK to skip the question and not ship `add-e2e-test`. Use judgment.
- **Skipping the foundation copy because "the user already has them".** They probably don't. Confirm by reading the target's `ai-tools/skills/` (if it exists at all). If it does have these, ask via `AskUserQuestion`: `Refresh from bundled copies (Recommended)`, `Keep what you have`.

## Verification

After all skills written:

1. Each `ai-tools/skills/<name>/SKILL.md` has frontmatter `name` + `description`, plus markdown body.
2. `name` field matches dir name.
3. No `{{PLACEHOLDER}}` strings survive in any output skill.
4. Foundation skills (plan-feature, create-spec, open-pr-from-context — plus create-qa-use-cases when e2e is enabled) reference each other correctly via `[name](../<name>/SKILL.md)` links. When e2e is disabled, confirm `plan-feature` has **no** surviving `<!-- e2e:start/end -->` markers and no dangling link to `create-qa-use-cases` / `add-e2e-test`.
5. **Plan-execution unit intact.** All five files exist; no surviving `<!-- include -->` markers; the conductor's `[implement-phase]` / `[review-phase]` / integrate-phase links resolve, and `amend-plan` + `systematic-debugging` (when shipped) → `[review-phase]` resolve. Under `ask`: both `integrate-phase-stacked` + `integrate-phase-modular` exist and the conductor's dispatch line names both.
6. `implement-plan` (+ the plan-execution sub-skills) cite real commands + the project's actual code host + branch / PR / co-author policy.
7. **`write-unit-test` (when shipped)** renders with no surviving `{{PLACEHOLDER}}`, cites the project's real test command, and every pack path in its `{{FRAMEWORK_PACK_LOADER_BLOCK}}` resolves to a file that actually landed under `ai-tools/skills/write-unit-test/resources/packs/`. One runner pack; stack packs only for frameworks the project actually uses (no `django.md` in a FastAPI repo, no web-framework pack in a pure-library repo). **Medplum projects**: confirm both FHIR-indexing setup files exist and are registered in `vitest.config.*` (`test.globalSetup` + `test.setupFiles`), `@medplum/definitions` is a devDependency, and no test re-indexes.
8. Setup script ([vinta-install-ai-tools-setup](../vinta-install-ai-tools-setup/SKILL.md)) runs cleanly with the new skills in place.