# Spike

> Create a hypothesis-driven, time-boxed spike ticket (Hypothesis/Budget/Kill Criteria/Disposition). Exempt from AgDR + coverage gates.

- Skill: `me2resh/spike` (Agent Skill)
- Install (CLI): `npx skillmds@latest add me2resh/spike`
- Raw SKILL.md: https://api.skillmd.com/api/skills/me2resh/spike/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Finance & Business
- Author: me2resh (https://skillmd.com/u/me2resh)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/me2resh/spike

---


# /spike — Create a Spike Ticket

Read `.claude/rules/writing-standard.md`. Use the **controlled technical writing profile** for the spike
ticket and disposition record. Keep the hypothesis and kill criteria concise.

Creates a structured GitHub Issue for a **spike** — a 1-3 day hypothesis-driven exploration whose goal is to answer a technical question with minimum investment, with the explicit understanding that the code will be discarded or substantially rewritten once the answer is clear.

> **When to use a spike vs a feature.** If you can answer the question through reasoning alone, file a `/feature`. If you genuinely don't know whether an approach will work — does this library scale, does this UX make sense, will this integration handle our load — file a `/spike` first. The spike's output is the answer; once it's in, file a fresh `[Feature]` ticket with production-shaped delivery (or a memo if the answer was no). See `workflows/sdlc.md` § Phase 1 for the gating heuristic.

## Path resolution

Read the registry path via `portfolio_registry`, the per-project docs dir via `portfolio_projects_dir`, and the ideas backlog via `portfolio_ideas_backlog` — all from `.claude/hooks/_lib-portfolio-paths.sh`. Source the helper at the top of any bash block that touches those paths:

```bash
source "$(git rev-parse --show-toplevel)/.claude/hooks/_lib-read-config.sh"
source "$(git rev-parse --show-toplevel)/.claude/hooks/_lib-portfolio-paths.sh"
registry=$(portfolio_registry)
```

Defaults match today's single-fork layout (`./apexyard.projects.yaml`, `./projects`, `./projects/ideas-backlog.md`). Adopters in split-portfolio mode override the `portfolio.{registry, projects_dir, ideas_backlog}` keys in `.claude/project-config.json`. Don't hardcode literal `apexyard.projects.yaml` or `projects/` paths in bash blocks — the helper resolves whichever mode the adopter is in. See `docs/multi-project.md`.

## Usage

```
/spike Will library X handle 10k events/sec
/spike Does Postgres LISTEN/NOTIFY scale to N consumers
/spike Can we replace Auth0 with Cognito
```

## Process

### 0. Write the active-issue-skill marker (REQUIRED — me2resh/apexyard#268)

Before any `gh issue create` (or other tracker CLI), write this skill's name to the active-issue-skill marker so `require-skill-for-issue-create.sh` lets the command through. At skill entry:

```bash
# Resolve the ops-fork root the SAME way the hooks do (_lib-ops-root.sh):
# anchor on the .apexyard-fork marker (split-portfolio v2 — onboarding.yaml
# lives in the sibling portfolio repo, NOT the ops fork), falling back to the
# onboarding.yaml + apexyard.projects.yaml pair (single-fork v1).
ops_root="$PWD"; r="$PWD"
while [ "$r" != / ]; do
  if [ -f "$r/.apexyard-fork" ] || { [ -f "$r/onboarding.yaml" ] && [ -f "$r/apexyard.projects.yaml" ]; }; then
    ops_root="$r"; break
  fi
  r=${r%/*}
done
mkdir -p "$ops_root/.claude/session"
echo "spike" > "$ops_root/.claude/session/active-issue-skill"
```

Remove the marker on **every** exit path (success, early-exit, user cancel, error):

```bash
rm -f "$ops_root/.claude/session/active-issue-skill"
```

The `clear-issue-skill-marker.sh` SessionStart hook sweeps stale markers from killed sessions, but a clean exit should never leave one behind. See AgDR-0030.

### 1. Resolve the target repo

Read `.claude/session/current-ticket` to determine which repo we're working in. If no active ticket, check `apexyard.projects.yaml` for managed projects. If only one project, use it. If multiple, ask:

```
Which project is this spike for?
```

If no projects are registered, ask for the repo in `owner/repo` format.

### 2. Verify the prefix is on the whitelist

Read `.ticket.prefix_whitelist` from `.claude/project-config.*.json`. If `Spike` (case-insensitive) is not in the list, warn and stop:

```
This fork's ticket schema doesn't include 'Spike' as a valid prefix.
Either add it to .claude/project-config.json → .ticket.prefix_whitelist, or
file the ticket using whichever prefix the fork uses for exploration work.
```

(The shipped default in `.claude/project-config.defaults.json` includes `Spike`. This check exists for forks that have customised the whitelist — see apexyard#109.)

### 3. Parse or ask for the title

Take the title from `$ARGUMENTS`. If empty, ask:

```
What's the spike? Give me a short title.
```

### 4. Gather details (one question at a time)

Ask conversationally — do NOT batch all questions. Wait for each answer before asking the next. The four fields below are all required; the fifth is optional.

**a) Hypothesis (required)**

```
What's the single specific question this spike answers?
Format: "We believe {X}. We will know we're right when {Y}."
(One sentence. If you can't write it that way, the spike isn't ready —
push back and ask the user to refine.)
```

If the user gives a casual hypothesis, restructure it into the required format and confirm.

**b) Budget (required)**

```
What's the hard cap on time/effort? Examples:
  - 2 days of one engineer
  - 1 sprint
  - Until end of week

At the budget cap, the spike ENDS regardless of outcome — that's the
commitment. What's yours?
```

Reject vague answers ("a while", "as long as it takes") — push for an explicit time/effort cap.

**c) Kill Criteria (required)**

```
Under what specific conditions does the spike STOP early?
(Either because the answer is in, or because pursuing further is wasted.)
```

Encourage at least two kill criteria — one "answered: yes" and one "answered: no / unworkable". Don't accept "we'll figure it out".

**d) Disposition (required — PROMOTE or DISCARD)**

```
What happens when the spike closes — PROMOTE or DISCARD?

  PROMOTE — if the hypothesis is confirmed, file a fresh [Feature] ticket
            with production-shaped delivery, port the relevant findings.
  DISCARD — if the hypothesis is rejected (or the answer is "we shouldn't
            do this"), delete the spike branch and file a memo with what
            we learned.

"Decide later" is NOT allowed — that's how spike code rots into half-
shipped production. Pick one.
```

If the user says "decide later" or "depends", explain the rule and ask again. The author must commit to one path in advance.

**e) Approach (optional)**

```
Any sketch of the exploration plan? (or press Enter to skip)
NOT a tech design, NOT a PRD — a few bullets: smallest test, tools needed,
boundary you'd cross to fail-fast.
```

### 5. Show the formatted ticket for confirmation

Resolve the spike body template via the portfolio helper so adopter overrides win when present:

```bash
source "$(git rev-parse --show-toplevel)/.claude/hooks/_lib-read-config.sh"
source "$(git rev-parse --show-toplevel)/.claude/hooks/_lib-portfolio-paths.sh"
template=$(portfolio_resolve_template tickets/spike.md)   # → custom-templates/tickets/spike.md if present, else templates/tickets/spike.md
```

Single-fork adopters (no `portfolio` block) and adopters with no override fall straight through to `templates/tickets/spike.md`. Adopters who want a customised spike-body shape drop their version at `<private_repo>/custom-templates/tickets/spike.md`. See `templates/README.md` for the path-mirroring convention.

**Backward-compat fallback**: if `portfolio_resolve_template` returns empty (template file missing — partial adopter setup or pre-#281 layout where the file lived at `templates/spike.md`), fall back to the inline heredoc body below and print a one-line WARN on stderr (`WARN: tickets/spike.md template missing — using inline fallback`).

Display the full ticket using the resolved template's section headings (the default `templates/tickets/spike.md` shape is reproduced below):

```
Here's the ticket I'll create:

---
**[Spike] {title}**

## Hypothesis
{hypothesis}

## Budget
{budget}

## Kill Criteria
- {criterion 1}
- {criterion 2}
- ...

## Disposition
{PROMOTE | DISCARD}
{one-sentence rationale}

## Approach
{approach or "—"}
---

Labels: spike
Repo: {owner/repo}

Suggested branch when you start work:
  spike/<TICKET-ID>-{slug}

Create this ticket? (yes / edit / cancel)
```

### 6. Handle response

- **yes** / **looks good** / **go** → create the issue
- **edit** / **change X** → ask what to change, update, re-show
- **cancel** / **no** → abort

### 7. Create the issue (via the tracker abstraction)

Dispatch creation through `tracker_create` (#670 / AgDR-0072, extended by
the #709 creator sweep) so the spike lands in **this project's** tracker — GitHub,
GitLab, or a `custom` CLI — per its `tracker:` block in `apexyard.projects.yaml`.
For a GitHub adopter this runs `gh issue create` exactly as before.

```bash
# Resolve the tracker lib (it lives in the ops fork's hooks dir) by walking up
# from the cwd; source it.
tracker_lib="$(r="$PWD"; while [ -n "$r" ] && [ "$r" != / ]; do \
  [ -f "$r/.claude/hooks/_lib-tracker.sh" ] && { echo "$r/.claude/hooks/_lib-tracker.sh"; break; }; \
  r="${r%/*}"; done)"
# shellcheck source=/dev/null
. "$tracker_lib"

# Ensure the `spike` trigger label exists on the target tracker. Best-effort
# (tracker_label_ensure always exits 0) — a duplicate/missing label never aborts
# the create. The label is what downstream hooks (AgDR-required hooks, coverage
# gates) read to apply the spike workflow exemptions.
tracker_label_ensure "{owner/repo}" "spike" "FBCA04" "Throw-away exploration; exempt from AgDR + coverage gates"

# Pass the body via a file (arbitrary markdown — never inline-interpolated).
body_file="$(mktemp)"
cat > "$body_file" <<'BODY'
{formatted body}
BODY

# tracker_create <owner/repo> <title> <body_file> [<labels_csv>] → {"ref","url"}.
# It is gated by require-skill-for-issue-create.sh; the active-issue-skill marker
# written at skill entry keeps this call allowed.
result="$(tracker_create "{owner/repo}" "[Spike] {title}" "$body_file" "spike")"
rc=$?
rm -f "$body_file"
if [ "$rc" -eq 3 ]; then
  # tracker.kind=none (shape-only): no tracker to create in. tracker_create
  # printed the rendered ticket body to stdout — surface it for manual /
  # external filing instead of a non-existent CLI/auth failure.
  echo "Tracker is 'none' (shape-only) — nothing was created in a tracker." >&2
  echo "File this in your external system (e.g. Jira/Linear via MCP):" >&2
  printf '%s\n' "$result"
  exit 0
elif [ "$rc" -ne 0 ] || [ -z "$result" ]; then
  echo "Spike creation failed — check the tracker CLI / auth. Nothing was created." >&2
  exit 1
fi

ref="$(printf '%s' "$result" | jq -r '.ref')"
url="$(printf '%s' "$result" | jq -r '.url')"
```

The `spike` label is the trigger that downstream hooks (AgDR-required hooks,
coverage gates) read to apply the workflow exemptions.

### 8. Return the URL + branch suggestion

Use the `ref` / `url` parsed from `tracker_create` in step 7:

```
Created: {owner/repo}#${ref} — {title}
${url}

When you start work:
  /start-ticket {owner/repo}#${ref}
  git checkout -b spike/GH-${ref}-{slug}
```

### 9. Remind the operator about the disposition gate

```
When the spike closes, run /spike-close to record the disposition:
  /spike-close --promote   # hypothesis confirmed → file a [Feature]
  /spike-close --discard   # hypothesis rejected → write a memo

Closing the spike without running this gate is allowed (closure is the
operator's call) but skips the memo / promotion artefact and leaves no
record of what was learned.
```

## Rules

1. **One question at a time.** Never batch questions. Wait for each answer.
2. **Always confirm before creating.** Show the full ticket and get explicit "yes".
3. **All four required fields are mandatory.** Hypothesis, Budget, Kill Criteria, Disposition — none can be skipped or deferred.
4. **Disposition is PROMOTE or DISCARD only.** "Decide later" is not allowed; reject it and re-ask.
5. **Labels.** `spike` always. Priority labels (P0/P1/etc.) are NOT applied — spikes are time-boxed by Budget, not prioritised by P-class. The accepted prefix list reads from `.claude/project-config.*.json` → `.ticket.prefix_whitelist`; `[Spike]` must be in that list.
6. **Branch suggestion.** Always `spike/<TICKET-ID>-<slug>`. The branch name is what AgDR-exemption hooks check (alongside the active-ticket marker).
7. **No PRD, no tech design.** Spikes describe a hypothesis, not a feature. Don't ask for user stories, ACs, or design notes — those belong on the follow-up `[Feature]` ticket if the disposition is PROMOTE.

## Workflow exemptions for spike work

A spike PR is exempt from the production SDLC subset listed below; everything else still applies. The exemptions are mechanical (hooks detect `[Spike]` prefix or `spike` label and skip), not advisory.

| Gate | Production work | Spike work |
|------|----------------|------------|
| Pre-Build (parent epic, story tickets, ACs, design review) | Required | Skipped — the spike ticket IS the unit |
| AgDR for technical decisions (`require-agdr-for-arch-pr.sh`, `require-agdr-for-arch-changes.sh`) | Required | Skipped — ship a memo on `/spike-close --discard` instead |
| Test coverage > 80% | Required | Skipped — coverage is irrelevant for throw-away code |
| Code Reviewer agent (Rex) | Required on every PR | Required — even throw-away code gets a sanity check |
| Security Auditor (auth/crypto/secrets diff) | Required | Required — security gates fire regardless of intent |
| Glossary in PR body | Required | Required — spike PRs explain WHAT WAS LEARNED, which is the artefact |
| QA Engineer verification | Required (AC verification) | Required (Hypothesis verification: did we answer the question?) |
| Disposition decision before close | N/A | Required — operator must declare PROMOTE or DISCARD via `/spike-close` |

See `.claude/rules/workflow-gates.md` § Spike work for the rule statement, and AgDR-NNNN-spike-skill-schema-and-exemptions.md for the rationale.

---

*Part of [ApexYard](https://github.com/me2resh/apexyard) — multi-project SDLC framework for Claude Code · MIT.*

