---
name: crystal-cut
description: "Attempt to close (cut) a crystal workitem — transition status:in-progress → status:done. Sweeps all unchecked `- [ ]` items; blocks if any remain (Decision Log #4). Use when work on a crystal is complete and you're ready to finalize."
license: MIT
---

# crystal-cut - Close a Crystal

## Purpose

Transitions a crystal from `status: in-progress` to `status: done`. The
crystal-completion-guard PreToolUse hook
(`${CLAUDE_PLUGIN_ROOT}/scripts/crystal-completion-guard.sh`) enforces the
gate at the tool layer: any `- [ ]` checkbox in the workitem blocks the
transition (Decision Log #4 generalizes "completion discipline" — every
unchecked checkbox is an open obligation, not only sidetracks).

This skill is the orchestration layer above the gate: it sweeps the
workitem, surfaces the open items with the five resolution paths, then
performs the flip once the user has addressed each.

## Gate behavior (defense in depth)

Three layers enforce the same invariant (Decision Log #7):

1. **Primary** — PreToolUse hook on Write/Edit/MultiEdit for workitem
   files. Works in any project regardless of git. It evaluates the
   *post-edit* content, so it also fires on the **creating** Write: a brand-new
   file written straight at `status: done` must contain zero `- [ ]`, or the
   gate blocks it. (Relevant when importing an already-complete legacy doc —
   see `crystal-grow` → Migrating legacy docs.)
2. **Stop reminder** — `${CLAUDE_PLUGIN_ROOT}/scripts/crystal-stop-reminder.sh`
   surfaces open items at end-of-turn so the assistant doesn't drift.
3. **Backup (git only)** — pre-commit hook ships with `vdm-git` for
   downstream projects with git. See `/vdm-git:guard` for activation.

The gate is intentionally cheap to circumvent only via the five
resolution paths — there is no `--force` flag and no `--no-verify` shortcut
in this skill. If the gate seems wrong for a specific case, the right move
is to use one of the five paths (most likely `cancelled (reason: ...)`),
not to bypass.

### Terminal-tier gates (DL #10 in crystal-multi-root)

The `done` transition triggers the unchecked-items gate above. Two other
terminal statuses behave differently:

| Terminal status | Gate                                                                  |
|-----------------|-----------------------------------------------------------------------|
| `done`          | Unchecked-items gate (see above) **+** orphan-sidetracks gate (see below). Five resolution paths apply. |
| `superseded`    | Requires `superseded-by: <slug>` in frontmatter — names the replacement. Gate blocks transition without it. The replacement workitem should cross-link back (`migrated from: <this-slug>` in body or `superseded: <this-slug>` in frontmatter). |
| `cancelled`     | No gate — author explicitly drops the work; unchecked items are no longer obligations. Record rationale inline (typically in a closing `## Cancelled` paragraph). |

The `superseded-by` requirement is enforced by the same PreToolUse hook
(`crystal-completion-guard.sh` → `crystal-completion-guard.py`) — see DL #10
for the gate design.

### Orphan-sidetracks gate (DL #14 in crystal-multi-root)

A sidetrack card can have `**Status:** open` without a corresponding inline
`- [ ] см. Sidetrack #N` marker in the workitem body. Without the marker,
the obligation is invisible to the checkbox-counting `unchecked-items`
gate — `crystal-cut` would silently pass over it. DL #14 declares the
inline-marker requirement; this gate enforces it deterministically.

Logic: scan every sidetrack card heading `### #N. <title>` followed at some
point by `**Status:** open[ ...]`. For each, look for a matching marker
line `- [ ] ... Sidetrack #N` anywhere in the body. Cards lacking a marker
are reported as orphans and block the done-transition with the combined
diagnostic (orphans listed alongside unchecked items if both exist).

Decision-Log entries use a different heading shape (`### #N / date / title`,
slash separator, no period after N) and never carry `**Status:**`, so the
parser does not collide with them.

Fix path for orphans: add the marker line (typically inside a
`## Pending sidetracks` block under `## Next actions`, or at the spot of
origin if the побег surfaced in body text), then resolve it via one of the
five paths above. The marker turns the invisible obligation into a visible
checkbox the other gate already understands.

Implementation:
- bash `audit_sidetracks_without_markers <workitem-path>` in `lib/crystal-path.sh`
- python `_audit_sidetracks_without_markers(content)` in `crystal-completion-guard.py`
  Both must stay in sync — they implement the same parser.

## Five resolution paths (DL #9)

Each unchecked item must be addressed by one of:

| Path                              | When to use                                            |
|-----------------------------------|--------------------------------------------------------|
| `[x]` resolved                    | Done within this workitem — flip the checkbox          |
| `migrated → <slug>`               | Belongs elsewhere — move card to an existing crystal **or a new sibling you grow on the spot**, cross-link both sides |
| `cancelled (reason: ...)`         | Explicitly dropped — record rationale                  |
| `deferred (deadline: YYYY-MM-DD)` | Postponed to a date — surfaces again on/after deadline |
| `promoted-to-stem (→ <sibling>)`  | Побег outgrew stem — split into sibling crystal        |

For `migrated` / `cancelled` / `deferred` / `promoted-to-stem`: the
sidetrack card's `**Status:**` line is updated AND the inline `- [ ]`
marker is checked (`[x]`) since the obligation has been addressed in a
defined way. The gate counts checkboxes, not statuses — keeping the two
in sync is required.

## Behavioral protocol

When `/vdm:crystal-cut [slug]` is invoked:

### Step 1: Locate the target

If `slug` is supplied:
- **Single-root mode:** target `<root>/<slug>/workitem.md` (canonical) or
  `<root>/<slug>.md` (legacy flat).
- **Multi-root mode:** slug is expected as `<root-qualifier>/<slug>` (per
  DL #6 in crystal-multi-root, e.g. `auth/refactor-jwt`). If only the
  short form is supplied and it's unambiguous (matches in exactly one
  root), use it; if it matches in ≥2 roots, ask which root.

If omitted, target the singleton active workitem (the one with
`status: in-progress`). Multiple actives → consult
`derive_singleton_mode`:
- `global` violation → warn and ask which one.
- `per-root` mode → one active is allowed per root; if pwd is under a
  resolved root, target that root's active; otherwise ask.
- `off` mode → ask explicitly which slug.

### Step 1.5: Harvest check — the `docs-distill` handoff

**Must run before the sweep, not after.** `docs-distill` *writes new `- [ ]`
obligations* into the workitem; sweeping first would clear a list that is about
to grow.

A crystal in the work phase produced artifacts that went outward. Its findings
about **the subject** — not about the conversation — are the whole reason it
existed, and they die with it unless they are harvested. The forcing function
here is normally a human who happens to notice; that is exactly what this step
replaces.

Applies when the crystal shows evidence of the work phase:

- `session-type: prd-work` (or any non-capture type), **or**
- the workitem's `## Текущая модель` / `## Decision Log` holds claims about how
  the subject now works — as opposed to which option the conversation picked.

A pure capture-phase crystal (`session-type: prd-prep`, may well stay dormant
forever) owes no harvest — the obligation is born with the *work*, not with the
crystal. Skip this step for those.

Nudge, do not gate:

```
🧪 Crystal `<slug>` is in the work phase — was the harvest given?

Findings about the subject outlive this crystal; findings about the
conversation do not. Run /vdm:docs-distill to pull the survivors out and give
each an address, or say "nothing to harvest" and proceed.
```

If the user runs it, `docs-distill` appends `- [ ] <finding> → <address>` items
to `## Next actions`. From there **the existing gate does the enforcing** — the
crystal cannot be cut while an unchecked box remains. Soft until named; binding
the moment it is named. That is the whole mechanism; no new gate is added here.

### Step 2: Sweep unchecked items

Read the workitem. Collect every `- [ ]` line with its line number and the
section it appears under (`## Next actions`, `## Sidetracks → ### #N`,
inline Decision-Log discussion, etc.). Group by section for readability.

### Step 3: Present the sweep

```
🔪 crystal-cut sweep: <slug>

  ## Next actions (3 open):
    L478:  - [ ] Скаффолд hook scripts
    L479:  - [ ] Регистрация в hooks.json
    L482:  - [ ] PROJECT_CHANGELOG.md entry

  ## Sidetracks (1 open):
    #6. Reflexive case — этa сессия как канонический пример (L431)

Resolve each via one of: [x] resolved | migrated → <slug> |
cancelled (...) | deferred (date) | promoted-to-stem (→ <sibling>).
```

For each open item, propose the most likely resolution path based on
context (e.g. obviously-completed items → suggest `[x]`; long-deferred
items the user mentioned punting → `deferred`). Do not auto-flip without
confirmation — the user owns each path decision.

### Step 4: Apply resolutions

For each addressed item:
- `[x]` resolved → Edit the checkbox in place
- `migrated` → create card in target crystal (cross-link both ways), flip
  this checkbox `[x]`, update sidetrack `**Status:**`
- `cancelled` → flip checkbox `[x]`, update sidetrack `**Status:**`
  with the reason
- `deferred` → flip checkbox `[x]`, update sidetrack `**Status:**` with
  the deadline
- `promoted-to-stem` → run `crystal-grow` for the sibling, transfer the
  обязательство there, flip checkbox `[x]`, update sidetrack `**Status:**`

### Step 5: Flip status

Edit the frontmatter: `status: in-progress` → `status: done`. The
PreToolUse hook will let this through because the unchecked count is now
zero. If the hook still blocks, you missed an item — re-sweep.

Also bump `last-updated:` to today.

### Step 6: Soft hints (DL #22)

After a successful close, emit two soft hints — text-only, no auto-trigger:

```
✓ Crystal `<slug>` closed (status:done).

Consider:
  - /vdm:changelog — record this workitem's outcome in PROJECT_CHANGELOG.md
```

If the close addressed `N` resolved (not migrated/cancelled/deferred)
sidetracks where `N > 0`, additionally:

```
  - /vdm:learn — N resolved sidetracks may be candidates for knowledge capture
```

These are suggestions the user runs explicitly. Never auto-invoke.

## Examples

### Example 1: Clean close

User: `/vdm:crystal-cut auth-refactor`

Sweep finds zero unchecked items. Assistant flips frontmatter, emits the
two soft hints. Done in one round.

### Example 2: Resolve via mixed paths

Sweep finds 4 open items. Assistant proposes:
- "Update README" → `[x]` (already done in the last commit)
- "Add tracing" → `migrated → observability-pass`
- "Fix typo in deprecated API" → `cancelled (API will be removed in v3)`
- "Mobile UI variant" → `deferred (deadline: 2026-06-01)`

User confirms each. Assistant applies edits + flips status + emits hints.

### Example 3: Gate fires on premature cut

User flipped `status: done` manually in IDE without using `crystal-cut`.
The PreToolUse hook intercepts and emits the blocked-diagnostic. The
assistant explains the five paths and runs the sweep above to bring the
user back on rails.

### Example 4: Done with one loose end — split it into a new sibling

The work is feature-complete except a single trailing obligation that has no
existing home — e.g. `zero-inbox` shipped, but a commented-out cron line
needs a real scheduling pass later. The sweep finds one `- [ ]`. There is no
"done but for one thing" state — and there shouldn't be; the obligation has
to land somewhere. Decide among three paths:

- date-bound ("revisit after the next release") → `deferred (deadline: ...)`
- genuinely dropped → `cancelled (reason: ...)`
- a real follow-up with no home → **`migrated → <new-sibling>`**

The third is the case worth spelling out, because the target doesn't exist
yet:

1. `crystal-grow cron-scheduling-pass` — create the sibling first (it starts
   `status: in-progress`; this is fine, the singleton you're closing is about
   to go `done`).
2. Move the obligation into the new crystal's `## Next actions` (or a
   sidetrack card), and cross-link: the source card's `**Status:**` becomes
   `migrated → cron-scheduling-pass`, the new crystal notes `migrated from:
   zero-inbox`.
3. Flip the source `- [ ]` → `[x]` (the obligation is addressed — it now
   lives elsewhere).
4. Re-sweep: zero unchecked → flip `status: done`. Cut succeeds.

Don't grow a whole sibling for something that's really a `deferred` or
`cancelled` — a one-line follow-up rarely earns its own crystal. Reach for
migrate-to-new only when the loose end is itself a unit of work.

## Quality gates

- [ ] Harvest check offered for a work-phase crystal (Step 1.5), **before** the sweep
- [ ] All `- [ ]` checkboxes addressed (gate would block otherwise)
- [ ] Sidetrack `**Status:**` lines match their checkbox resolutions
- [ ] `migrated` items have cross-links in both source and target
- [ ] Frontmatter `status: done` AND `last-updated: <today>`
- [ ] Two soft hints emitted (always changelog; learn if N>0 resolved sidetracks)

## Configuration

Shares the `crystal` config section with the other three crystal-*
skills. See `/vdm:crystal-grow` for sub-commands. Disabling
(`crystal off`) silences the reminder hooks but does **not** disable the
PreToolUse gate — the gate is unconditional. To fully disable, uninstall
the plugin.

## Reflexive case

The suite's own design crystal — upstream at
`cc-vdm-plugins → docs/tasks/crystal-design/workitem.md`, not in your project —
was closed via this exact protocol when the crystal-* suite reached v1. The act
of closing it validated the gate, the sweep, and the hint emission end-to-end.
Read its final state
for a worked example of a clean cut.
