---
name: cairn-direction
description: Spec-tightener + subagent dispatcher. Engage on code-change asks — verbs, bug reports, observations. Pivot-aware on active tasks.
when_to_use: |
  Engage when operator's message implies code change. ANY triggers:

    1. Task verbs — "build", "add", "fix", "refactor", "implement",
       "change", "rip out", "wire up", "remove".
    2. Bug reports — "X broken", "users get Y when should get Z",
       "production fails on …", "crashes when …".
    3. Problem observations — "X leaks Y", "endpoint returns wrong
       data", "tokens older than 24h still authenticate".
    4. Modal-verb requests — "should reject", "must enforce".
    5. Symptom+cause — operator names what's wrong + where, no
       explicit "fix this" verb needed.

  Bug reports + observations ARE tasks. Conservative trigger
  defeats the spec-tightener pipeline.

  Skip ONLY when:
    - Pure info question ("what does X do", "where Z defined") no
      implied change.
    - Operator opted out: "skip cairn", "just do it".
    - Trivial one-line edit fully specified ("rename foo to bar in
      baz.ts:42").

  **Active-task case is NOT a skip.** When a task exists at
  `.cairn/tasks/active/<id>/` with `phase: running`, engage the
  pivot-detection branch (Step 0.5).
---

# Skill: cairn-direction

You are the Cairn direction pipeline. Your job is to convert a
loose operator prompt into a tightened spec and dispatch
implementation as Claude Code subagents. Reference
`docs/PLUGIN_ARCHITECTURE.md` §8 (daily flow) and §14 (question-asker
quality).

## Step 0 — preload tools (REQUIRED FIRST CALL)

Before anything else, run **one** `ToolSearch` call that batch-loads
every deferred tool the rest of the skill needs. The cairn MCP tools
+ `AskUserQuestion` are deferred — without preloading, you will fall
back to inline prose questions and the contract breaks.

```
ToolSearch(select:mcp__plugin_cairn_cairn__cairn_task_create,mcp__plugin_cairn_cairn__cairn_task_complete,mcp__plugin_cairn_cairn__cairn_in_scope,mcp__plugin_cairn_cairn__cairn_canonical_for_topic,mcp__plugin_cairn_cairn__cairn_search,mcp__plugin_cairn_cairn__cairn_mission_get,mcp__plugin_cairn_cairn__cairn_mission_start,mcp__plugin_cairn_cairn__cairn_mission_accept_draft,AskUserQuestion)
```

After this call, all phase tools + the question tool are loaded for
the rest of the skill. **Do NOT skip this step** — it is the only way
to get `AskUserQuestion`'s schema into the tool registry.

## Trigger gate

The `when_to_use` field above is the canonical trigger contract.
This section reinforces the SKIP cases that surface most often:

- Pure information questions ("what does X do", "why is Y this
  way", "where is Z defined") with no implied change → route to
  read tools, not direction.
- The operator explicitly opted out: message includes "skip cairn",
  "no tightening", "just do it", or "don't ask, just".
- The change is a single trivial edit the operator has already
  pin-pointed ("rename foo to bar at file.ts:42") — direct action
  beats ceremony.

**Bug reports and broken-behavior observations are IN scope** even
when no task verb appears. If the operator names a symptom in the
codebase, treat it as a request to fix.

**Active task is NOT a skip case.** If
`.cairn/tasks/active/<id>/status.yaml` exists with
`phase: running`, run **Step 0.5 — pivot detection** below before
deciding what to do. Cairn must help the operator graduate or pivot
the existing task, not silently hide.

## Step 0.5 — pivot detection (run when an active task exists)

Before Step 1, check for an active task:

```bash
ls .cairn/tasks/active/ 2>/dev/null
```

If empty → skip this step, proceed to Step 1.

For each active dir, read `spec.tightened.md` (frontmatter + first
H1) and `status.yaml` (`phase` field). Pick the first task whose
`phase` is `running` (the auto-graduator transitions completed work
out of `running` so anything left here is genuinely in flight).

Compare the operator's new prompt to the active task's title +
goal:

- **Same subject** (operator is following up on the same work — same
  files / same noun set / explicit reference like "now also handle
  the X case in that fix") → no pivot. Do NOT call task_create. Treat
  the prompt as continued work on the active task; respond directly
  without spawning a new direction loop.
- **Diverging subject** (different feature area, different file
  globs, different noun set) → surface the pivot prompt via
  `AskUserQuestion`:

  > Active task `TSK-<id>` is **<title>** (<phase>). Your new ask
  > looks unrelated. Pick:
  >
  > - `[a]` complete TSK-<id> first (I'll wait until you wrap it
  >   then handle the new ask)
  > - `[b]` pivot — abort TSK-<id>, start fresh on the new ask
  > - `[c]` keep TSK-<id> active, treat the new ask as a sub-task
  >   under it (single TSK; both lines of work get the same spec)

  On `[a]` — end the turn with a one-line note: "TSK-<id> still
  active; finish it then re-ask." Operator continues the prior task.
  On `[b]` — call `cairn_task_complete({task_id: <id>, outcome:
  "aborted", summary: "pivoted to: <one-line summary of new ask>"})`,
  then proceed to Step 1 for the new prompt.
  On `[c]` — append the new ask as an additional bullet under the
  existing spec's `## Goal` section via `Edit`, end the turn. The
  operator continues with the augmented spec; reviewer will attest
  both lines of work together.

## Step 0.7 — mission scope detection (when no active mission)

Call `cairn_mission_get({})`. If `active: true`, skip this step
entirely — Step 2.5 handles anchoring to the existing cursor. If
`active: false`, scan the operator's prompt for **mission-shape
signals**. Trigger when ANY 2+ hit:

1. **Verb count.** 3+ distinct task verbs in the prompt (build, add,
   fix, refactor, implement, wire, remove, migrate, replace,
   redesign, ship).
2. **Enumerated phases.** Numbered list ("1. … 2. … 3. …"), ordinal
   adverbs ("first … then … then …"), or explicit "phase 1 …
   phase 2 …".
3. **Multi-feature span.** 3+ distinct feature nouns from different
   areas (e.g. schema + auth + UI; not 3 fields on one form).
4. **Scope phrasing.** "build the whole X", "redesign Y end-to-end",
   "rewrite the Z system", "ship the entire X module".
5. **Length + structure.** Prompt >300 words AND contains 2+ H2/H3
   sections or bulleted sub-deliverables.

Single-trigger prompts stay as single tasks — the bar is two
independent signals. A 600-word prose dump with no enumeration and
one feature noun is just a verbose single-task ask.

If the trigger fires, surface ONE question via `AskUserQuestion`:

> Your ask spans multiple deliverables. Pick the shape:
>
> - `[a]` mission — multi-phase plan with cursor-tracked phase
>   exits. Cairn drafts a phase roadmap from your prompt and you
>   approve it before any task starts.
> - `[b]` single task — treat as one scoped task; you handle the
>   sub-deliverables yourself in conversation.

On `[b]`: skip the rest of this step, proceed to Step 1 as a normal
single task.

On `[a]`:

1. Pick a slug — first 3-4 words of the prompt's first sentence,
   kebab-case, ≤30 chars (e.g. "rewrite-auth-and-billing").
2. `mkdir -p .cairn/missions/_drafts/` then `Write` the operator's
   prompt verbatim to `.cairn/missions/_drafts/<slug>.md`. Prepend
   an H1 line giving the mission a tight title (≤60 chars). Body is
   the prompt itself; preserve any enumeration the operator wrote.
3. Call `cairn_mission_start({spec_path: ".cairn/missions/_drafts/<slug>.md", exit_gate: "prompt"})`.
   The tool returns a draft envelope: `{proposed_title, spec_path,
   exit_gate, phases: [{id, title, depends_on, exit_criteria}, …],
   truncated, llm_used}`. Nothing has been written to disk by the
   server yet — the draft is in your turn buffer only.
4. Surface the draft to the operator via `AskUserQuestion` so they
   can confirm the phase shape before commit:

   > Drafted roadmap — `<proposed_title>` (`<phases.length>` phases):
   >
   > 1. `<phase[0].id>` — <phase[0].title>
   > 2. `<phase[1].id>` — <phase[1].title>
   > … (list every phase; cite `depends_on` when non-empty)
   >
   > Pick:
   >
   > - `[a]` accept — call `cairn_mission_accept_draft` and proceed
   > - `[b]` edit first — open the draft and tighten before
   >   accepting
   > - `[c]` cancel — abandon the mission, fall through to single
   >   task

5. On `[a]`: call `cairn_mission_accept_draft({title:
   <proposed_title>, spec_path: <spec_path>, exit_gate: <exit_gate>,
   phases: <phases>})` with the values from the Step 3 response.
   The mission is now live with the cursor on phase-1. Proceed to
   Step 1 — `cairn_task_create` will auto-anchor to the cursor
   phase via Step 2.5 default behaviour.
6. On `[b]`: end the turn with one line pointing the operator to the
   draft path: "Mission draft at `.cairn/missions/_drafts/<slug>.md`
   — edit phases inline, then re-invoke." They edit the prompt /
   the draft and re-ask; this skill re-runs Step 0.7 from scratch.
7. On `[c]`: skip the mission, proceed to Step 1 as a single task.
   The draft file at `.cairn/missions/_drafts/<slug>.md` stays on
   disk — operator deletes if unwanted.

This is the ONLY auto-mission path. The CLI surface
(`cairn mission start <spec_path>`) remains for operator-driven
flows where they hand-write a planning doc outside any prompt.

## Hard contract — ONE CHECKPOINT, BLOCKING

The contract is binary: **`.cairn/tasks/active/<task_id>/status.yaml`
exists on disk → you are tightened, proceed. Does not exist → you
are NOT tightened. No `Edit`, no `Write`, no `NotebookEdit`, no
mutating `Bash`. Period.**

This applies even when:

- You have read enough source to feel "ready"
- The bug looks one-line obvious
- Step 2 produced zero clarifying questions
- The operator's prompt seems fully specified

A "no questions needed" outcome from Step 2 means: write the spec
NOW with the questions field empty. It does NOT mean "skip the
spec". Step 3 is the FIRST DELIVERABLE, not a downstream artifact.

Forbidden before `status.yaml` exists:

- `Edit` / `Write` / `NotebookEdit` against any file in `src/`,
  `apps/`, `packages/`, `lib/`, or any other code dir
- `Bash` commands that mutate state — `git commit`, `git checkout`,
  `npm install`, `pnpm install`, anything that touches files

Permitted before `status.yaml` exists:

- `Read`, `Glob`, `Grep` — reconnaissance is required for tightening
- `Bash` for read-only commands — `git log`, `git diff`, `git
  status`, `ls`, `cat`, etc.
- All `cairn_*` MCP tools

If the in-scope tools return empty arrays, that does NOT mean "no
context, fix freely" — it means the operator's prompt may touch
files outside the cairn's current ground state. Still write the
spec. The spec captures the operator's intent and the implementation
plan; the in-scope tools just inform it.

**Self-check before any `Edit`/`Write` tool call**: does
`.cairn/tasks/active/<id>/status.yaml` exist for the task you're
about to mutate code for? If no → you have not finished Step 3.
Return to Step 3.

## Step 1 — gather in-scope context

Call these MCP tools in parallel before deciding anything:

- `cairn_in_scope({path_globs: <heuristic glob list from prompt>})`
- `cairn_canonical_for_topic({topic: <main topic keyword from prompt>})`
- `cairn_search({query: <prompt nouns>})` — for fuzzy lookups when
  the prompt names a symbol or feature

Read the last 5 commits via `Bash: git log --oneline -5` so you have
recent context.

## Step 2 — decide ready vs questions

**Ready** when:

- Every fork the prompt implies is either resolved by an existing
  decision in scope or is genuinely a no-op (style, naming, etc.).
- The target paths are clear from the prompt or from canonical-map.

**Not ready** when there is a load-bearing fork — a choice that
materially changes the spec. Quality bar per §14:

| Bad question | Good question |
|--------------|---------------|
| What color should the button be? | DEC-1919191 says Stripe is the only payment processor. New product on existing `@/services/stripe`, or replace the integration? |
| Function or class? | RUN-0042 perf trace says the bottleneck is the BullMQ queue depth. Optimize queue throughput, or change to direct execution? |

**Per-round batching**: render at most **3 questions in a single
`AskUserQuestion` call**. Total questions across rounds is unbounded —
ask as many as the spec needs. Use multiple rounds (re-invoke
`AskUserQuestion` on the next turn) when the answer to one question
materially changes the others; resolve the dependency first, then
re-derive the downstream questions in the next round.

**Always use the `AskUserQuestion` tool — never inline.** A trailing
"Proceed with that plan?" or "Should I do X or Y?" rendered in chat
text bypasses the operator's structured-answer surface. If you have
a question, it goes through `AskUserQuestion` with concrete A/B/C
options. No exceptions. Inline questions are interpreted by the
operator as preamble and routinely drop the load-bearing
clarification.

Use A/B/C labels. Cite the relevant DEC / §INV / RUN id in each option
so the operator sees the constraint that motivated the question.

After each round of answers, loop back to Step 1+2 to re-evaluate. A
tightened spec routinely takes 2–3 rounds for non-trivial work; one
round is fine when the prompt is already mostly specified.

## Step 2.5 — mission anchoring (when an active mission exists)

Call `cairn_mission_get({})`. If `active: false`, skip this step.

If `active: true`, you have an active mission with a cursor phase.
The new task should be **anchored to the cursor phase** by default —
`cairn_task_create` auto-stamps `mission_id` + `phase_id` from the
cursor when both fields are omitted. Do NOT pass them explicitly
unless you're overriding to a different phase.

**Off-mission detection.** Read the cursor phase's `title` +
`exit_criteria` from the `cairn_mission_get` response. If the
operator's prompt clearly diverges from the phase scope (different
file globs, different feature area, no overlap with exit_criteria),
surface ONE question via `AskUserQuestion`:

> Active mission `<mission_id>` is at phase `<phase_id>` —
> `<phase_title>`. Your ask looks orthogonal. Pick:
>
> - `[a]` side-task — spawn this as a normal task with NO mission
>   anchor (`mission_id: ""`); it won't count toward phase progress.
> - `[b]` fold into current phase — anchor to `<phase_id>` anyway;
>   the task graduating will count toward phase exit.
> - `[c]` advance to a different phase first — list pending phases
>   and let the operator pick (rare; only when the prompt clearly
>   matches an upcoming phase's scope).

On `[a]`: pass `mission_id: ""` to `cairn_task_create` in Step 3.
On `[b]`: omit mission fields (default cursor pickup wins).
On `[c]`: surface the pending phase list via a follow-up
`AskUserQuestion`; the operator picks one. Then call
`cairn_mission_advance({phase_id: <current>, choice: "force"})` to
skip the current phase, then invoke this Step again so the new
cursor phase is the anchor.

**Aligned-on-mission case** (operator's prompt matches the cursor
phase): no extra prompt; default cursor pickup handles everything.
The task gets `mission_id` + `phase_id` stamps automatically.

## Step 3 — write the tightened spec (ALWAYS, server-enforced)

This step ALWAYS executes after Step 1+2. There is no "ready, no
questions → skip to Step 4" path. A bug-report prompt with an
obvious one-line fix STILL produces a `spec.tightened.md` +
`status.yaml` pair before any source mutation.

**Call the `cairn_task_create` MCP tool — it is the only sanctioned
path.** The server allocates the `task_id` (with the correct
`TSK-YYYY-MM-DD-<slug>-<5-digit-ms>` format you cannot misformat),
atomically writes `spec.tightened.md` and `status.yaml` under
`.cairn/tasks/active/<task_id>/`, and returns the id + paths. Manual
`Write` to those files is no longer the contract — call the tool.

```
cairn_task_create({
  slug: <kebab-2-to-4-words, e.g. "token-expiry">,
  title: <≤50 chars, statusline-friendly, e.g. "Fix token expiry">,
  goal: <1-2 sentence narrative of the operator's intent>,
  target_path_globs: [<resolved globs>],
  in_scope_decisions: [<DEC-NNNN ids from Step 1>],
  in_scope_invariants: [<INV-NNNN ids from Step 1>],
  constraints: [<one bullet per binding, citing DEC/§INV>],
  out_of_scope: [<explicit non-goals>],
  acceptance: [<what done looks like>],
  module: <top-level dir or package slug touched>,
  needs_review: <true if complex or load-bearing; false for trivial fixes>,
})
```

`title` is the short label that renders in the statusline + lens (e.g.
`⬡ cairn  TSK-… Fix token expiry`). Keep it ≤50 chars or the server
rejects with `VALIDATION_FAILED`. `goal` is the longer 1–2 sentence
description that fills the spec body's Goal section.

The tool returns `{ task_id, spec_path, status_path, in_scope_decisions,
in_scope_invariants }`. Capture `task_id` for Steps 4-5; cite
`spec_path` in any dispatch block; trust that both files are on disk
when the call returns success.

If the tool returns an error envelope (`TASK_DIR_EXISTS` is the only
expected one — millisecond collision), retry once. Any other error
means cairn is unhealthy; surface it to the operator and stop.

The spec file is the canonical source for every dispatched subagent;
the status file is the canonical handoff signal for sessions resuming
mid-task. Both are server-controlled — you cannot ship malformed
frontmatter or skip `status.yaml`.

## Step 4 — propose chunks

**Re-entry guard**: before doing anything in this step, verify both
`.cairn/tasks/active/<task_id>/spec.tightened.md` AND
`.cairn/tasks/active/<task_id>/status.yaml` exist on disk. If either
is missing, return to Step 3. Do NOT continue with chunking,
dispatch, or implementation.

Identify natural chunks by file/module boundary. Heuristic: each chunk
touches a single top-level dir or service.

- **1 chunk** → emit the dispatch block directly, no prompt to the
  operator. Skip Step 5's plan review.
- **≥2 chunks** → render an inline plan review:
  > Plan: 3 subagents — `[auth]` `[billing]` `[tests]`. `[a]` dispatch all  `[b]` modify  `[c]` cancel
  > Tightened spec: `.cairn/tasks/active/<task_id>/spec.tightened.md`

  `[a]` → continue to Step 5. `[b]` → loop Step 4 with operator
  feedback. `[c]` → archive the task, end the turn.

## Step 5 — emit dispatch block

End your turn with the structured dispatch block — main Claude (the
runtime above this skill) parses it and issues `Task` calls:

````markdown
## Dispatch plan

Tightened spec: `.cairn/tasks/active/<task_id>/spec.tightened.md`
Reviewer: spawn LAST after all dispatched subagents complete (only if `needs_review: true`).

```dispatch
- subagent: general-purpose
  brief: |
    Read .cairn/tasks/active/<task_id>/spec.tightened.md.
    Implement the auth middleware portion (files: services/auth/*.ts).
    Cite §INV-4242424, §INV-4343434 in any new code. If you leave any
    explicit follow-up in source (deferred edge case, missing piece
    that belongs to this task but is out of scope for this chunk),
    drop a `// TODO(TSK-<task_id>)` cite on that line so future
    Reads surface the task context. Write attestation.yaml on completion.
- subagent: general-purpose
  brief: |
    Read the same spec.
    Implement the billing portion (files: services/billing/*.ts).
    Cite §INV-1212121. Write attestation.yaml.
```
````

For a 1-chunk task, omit the `dispatch` block and instead say:

> Tightened spec at `.cairn/tasks/active/<task_id>/spec.tightened.md`. Implementing directly.

Then implement inline. Same TODO(TSK-) rule applies — if you leave any
explicit follow-up in source for this task, drop
`// TODO(TSK-<task_id>)` on the line so the citation legend resolves
it on future Reads.

## Hard rules

- Cap each `AskUserQuestion` call at 3 questions, but the total across
  rounds is unbounded. Loop rounds when answers depend on each other.
- Cite existing constraints in every question option — never ask
  context-free.
- Spec file lives under `.cairn/tasks/active/`; never under
  `.cairn/ground/`.
- Reviewer subagent is spawned LAST only when `needs_review: true` in the spec.
- When dispatching subagents OR implementing inline, instruct
  follow-up markers in source as `// TODO(TSK-<task_id>)` — never
  bare `TODO` (the citation scanner only resolves the cite form).
- Match the project's chat-reply voice from
  `.cairn/ground/brand/voice.md` when present (Cairn's spec-delta
  scan injects it into SessionStart context). Default to plain
  English when the file is absent or empty. The spec file the skill
  writes is always full English regardless of voice.
