crystal-grow

star 0

Start (or promote) a crystal — an in-repo workitem under <root>/<slug>/workitem.md that anchors long-running sessions and prevents loss of sidetracks, decisions, and deferred promises. Roots auto-discovered (any `tasks/` dir, hidden segments excluded) or explicitly listed in config. Invoke when a session is widening into research, brainstorm, PRD work, or any flow likely to spawn 3+ branches.

VoDmAl By VoDmAl schedule Updated 6/3/2026

name: crystal-grow description: "Start (or promote) a crystal — an in-repo workitem under //workitem.md that anchors long-running sessions and prevents loss of sidetracks, decisions, and deferred promises. Roots auto-discovered (any tasks/ dir, hidden segments excluded) or explicitly listed in config. Invoke when a session is widening into research, brainstorm, PRD work, or any flow likely to spawn 3+ branches." license: MIT

crystal-grow - Start or Promote a Crystal

Purpose

Creates a new workitem (full crystal) under the configured crystal root, or promotes an in-flight shadow workitem to a full file-backed one. A crystal captures three things a chat transcript loses under context pressure:

  1. Sidetracks — observations and follow-ups that aren't the main goal.
  2. Decisions — what was chosen, what was rejected, and why.
  3. Open obligations — unchecked items that must be addressed before the workitem is "done."

The crystal-completion-guard hook enforces that the workitem cannot transition to status: done while any unchecked - [ ] items remain (Decision Log #4). The other three skills (crystal-bud, crystal-cut, crystal-cave) operate on the file this skill creates.

Three levels of depth (DL #12)

Not every session needs a workitem file:

  • Plain — simple Q&A. Nothing persisted.
  • Shadow — assistant tracks sidetracks in working memory; no file yet. Ready to promote on user request or when thresholds trip.
  • Full crystal — file exists, frontmatter status: in-progress, gate active. This is what crystal-grow creates.

The assistant starts in Plain, escalates to Shadow at the first scope-creep signal, and proposes promoting to Full when the auto-promotion threshold for the current session type is reached.

Auto-promotion threshold (DL #19)

The assistant classifies the session type from the first 1–3 user messages (ambiguous → conservative default other). The user can override at any time by editing the workitem's session-type: frontmatter — or by stating intent explicitly ("давай заведём", "это большая задача", "сделаем PRD"), which immediately promotes regardless of counter state.

Session type Promotion trigger (shadow → full)
brainstorm immediate (1 sidetrack OR 2 tool calls)
prd-prep immediate (from the first message)
prd-work already promoted (a workitem exists)
research 4+ tool calls OR 2+ sidetracks OR 15+ min
short-bug 6+ tool calls OR 3+ sidetracks OR 25+ min
maintenance 8+ tool calls
review 3+ sidetracks
docs-only 5+ tool calls AND 2+ sidetracks
other 5+ tool calls AND 2+ sidetracks (conservative)

Counters live in the assistant's working memory only — there's no state file. Restarting the session resets them; an active full crystal survives because it's in docs/tasks/.

When the threshold trips, propose explicitly:

Это похоже на <session-type> со сложностью выше порога. Предлагаю завести crystal: title=<draft>, slug=<kebab>. Содержимое shadow-сайдтреков сразу попадёт в Sidetracks. Создавать?

Forced-promotion signals (override the counter)

DL #19 above handles organic scope-creep — start small, grow past a gate. The signals below handle the opposite case: sessions born as planning work, where the counter lags and the plan ends up in chat instead of workitem.md. Any one triggers immediate promotion regardless of counter state. When two or more co-occur, propose grow before the substantive answer, not after.

(1) Trigger-phrase whitelist — immediate prd-prep / prd-work

If the user's message contains any of these, classify accordingly and propose grow:

  • RU: «нужен план», «дай план», «давай менять структуру», «миграция X», «оценить объём», «проведём X», «оцени изменения», «сначала план», «сделаем PRD», «распиши что будем менять», «разложи по этапам».
  • EN: «I need a plan», «let me see the plan», «migrate X», «restructure», «estimate scope», «walk me through the changes», «break this down», «PRD this», «scope this out», «start with a plan».

The list isn't exhaustive — any phrasing demanding a plan/PRD/scope/migration artifact before the work qualifies. False-positive promotion is cheaper than false-negative loss.

(2) Step-0 reflex — before the substantive answer

Before drafting a plan-shaped response, ask: «Если я отвечу сейчас — ответ попадёт в workitem.md или останется в chat?» If "stays in chat" and the response is plan-shaped, stop and propose grow first. Then write the substantive answer into the new workitem, not the chat.

Temporal order matters. The DL #19 flow says "when the threshold trips, propose explicitly" but doesn't specify before vs. after the first substantive answer. The first answer is the plan; if it lands in chat, the plan is born homeless and the next compaction takes it.

(3) Plan-shape anti-pattern — ≥5 / ≥3 / ≥2

If a chat draft contains any of:

  • ≥5 steps (numbered/bulleted procedure)
  • ≥3 architectural decisions (this-vs-that with rationale)
  • ≥2 HITL questions for the user

— that draft is already a workitem. Stop, propose grow, continue in-file. The next compaction collapses 5 steps to one synopsis sentence, decisions to "discussed alternatives", HITLs to "some open items remained". The cost of not relocating is the cost of the next compaction.

(4) Context-pressure escape valve

If the visible conversation crosses ~50% of the context window with no workitem for the current line of work, auto-propose grow. Frame as defensive, not procedural:

Контекст наполнился ~50% без workitem'а. Следующая компактизация съест decisions и побеги — останется только сводка. Предлагаю завести crystal сейчас. Slug?

Why "forced" matters

DL #19 is organic-creep detection: fires when a session that started small grew past a counter. The four signals above are first-sight detection: fire when the session was born as planning. Both modes are real and need separate hooks. Field report (the session that shipped multi-root): all four signals were overdetermined from message #1 — explicit «давай проведём миграцию», explicit «сначала нужен план», 5+ steps in the first substantive response, 2+ HITLs in that same response. Yet counter-based logic kept the workitem in shadow for three turns because no counter threshold had ticked. The plan was drafted in chat when it should have been in workitem.md from the start.

Working inside an active crystal (in-flight discipline)

crystal-grow is the creation skill, but the file it produces only earns its keep if the assistant uses it during work. Field experience shows the common failure mode: a session works on an active crystal for hours, makes several architectural decisions, finds several побеги — and never opens workitem.md. At the next context compaction the decisions collapse to "discussed alternatives", the побеги collapse to "noted some follow-ups", and the work that justified having a crystal at all dies in chat.

The four crystal-* skills are not just commands — they're a discipline. The discipline runs continuously while a crystal is active, not only at grow/cut moments. Two complementary hooks remind the assistant:

  • crystal-hydrate.sh at SessionStart — surfaces the active workitem(s) with the source-of-truth reminder.
  • crystal-capture-reminder.sh at UserPromptSubmit — fires when source edits accumulate without workitem.md being touched (smart mode default; per-session throttle).
  • crystal-stop-reminder.sh at Stop — emits the work-without-capture nudge at end-of-turn when source files are newer than workitem.md.

Below is the explicit checklist the hooks are pointing at. Read it once when working inside an active crystal — the discipline is small and mechanical, but it has to happen every turn.

What goes into ## Decision Log (DL entry)

Anything chosen-over-alternative-with-rationale. Specifically:

Trigger Why a DL entry matters
Chose X over Y after weighing alternatives Future reader sees why X, not just that X happened
Raised/lowered a threshold or tolerance Future reader otherwise sees a magic number with no context
Deviated from an earlier plan in this same workitem Without it, the Next-actions list silently lies
User confirmed a non-obvious choice (validated, not corrected) The validation is signal — preserves judgment, not just code
Single-instance exception to a documented rule Without it, the exception looks like inconsistency
Dropped/cut work that was earlier listed as in scope Scope shrinkage needs to be explicit, not a silent disappearance

Format (mirrors existing entries in docs/tasks/crystal-design/workitem.md):

### #N / YYYY-MM-DD / <short title>

**Source:** user | assistant | both
**Context:** <one paragraph — what was on the table>
**Why:** <one paragraph — the reasoning that selected this option>
**Consequence:** <what changes downstream because of this choice>

Do not write DL entries for: routine implementation steps, obvious choices, mechanical refactors. The bar is "future reader, six months from now, opening this file cold, would need this to understand the work."

What goes into ## Sidetracks (побег)

Anything that surfaces during work and isn't the current main goal. See /vdm:crystal-bud for the card format. Concrete triggers:

Trigger Capture as побег because
Adjacent code observation (TODO, smell, stale comment) Loss would forfeit a found-while-here fix
Ecosystem block (lib X requires Y v8, breaks A) Future migration attempts will hit the same wall
"We should also..." / "later we'll need..." The "later" never comes back unless captured
Implicit dependency noticed mid-work Documents the actual coupling, not the assumed one
Failed attempt with useful diagnosis Saves the next attempter from re-deriving why X didn't work
Bug in tooling/dep with a workaround applied here Workaround needs context for future "is this still needed" review

If unsure — capture. Bud is cheap; a missed бег is the whole reason this suite exists.

Per-turn cadence

The mechanical question to ask yourself at the end of each significant work segment (not every turn — but anytime a real chunk landed):

Что из того, что мы сейчас сделали или обсудили, нужно перенести в workitem.md? Decisions → DL. Observations → побеги. Closed Next-actions → flip - [ ][x].

The capture-reminder hook will ask you this when source files are newer than workitem.md. Treat its appearance as a contract reminder, not noise.

Configuration

The hooks honor crystal.capture-mode in .claude/vdm-plugins.json:

Mode Behavior
smart Default. Fires only when source edits accumulate without workitem touch, throttled per session (default 600s, override via crystal.capture-throttle).
proactive Fires every UserPromptSubmit while an active workitem exists. Use during onboarding or when discipline isn't yet internalised.
silent Never fires. Use only after the discipline is fully internalised — the cost of a missed капчер is the cost of the suite.

Storage layout (DL #2, #18, #20, #12 in crystal-multi-root)

  • Roots resolve through resolve_crystal_roots() in ${CLAUDE_PLUGIN_ROOT}/lib/crystal-path.sh, in priority order:
    1. crystal.paths (array of globs) — explicit override.
    2. crystal.path (string, legacy single root) — back-compat.
    3. Auto-scan — any tasks/ directory under project root, excluding hidden segments (.git/, .stversions/, .obsidian/, etc.) and common dependency dirs (node_modules/, vendor/). This is the default and works for classic single-root setups (finds docs/tasks/) AND monorepo / vault layouts (finds packages/*/tasks/, etc.).
  • Folder-style (canonical, DL #12 in crystal-multi-root): <root>/<slug>/workitem.md plus optional <root>/<slug>/references/, <root>/<slug>/attachments/, etc. This is the only layout we recommend for new workitems.
  • Flat-style (legacy): <root>/<slug>.md — recognized for back-compat with pre-suite single-file notes; the gate still applies. Don't create new flat workitems — promote to folder-style during onboarding.
  • Leaf is tasks/ (DL #1 in crystal-multi-root) — not configurable. Projects using tickets/, issues/, workitems/ are out of scope.

Cross-referencing workitems (wikilink form)

After folder-migration all workitems share basename workitem.md. Bare [[<slug>]] wikilinks don't resolve under Obsidian's shortest-path matcher (multiple workitem.md candidates → resolver gives up). Use disambig form:

[[<slug>/workitem|<slug>]]

Applies to:

  • body wikilinks within other workitems / references / vault notes
  • frontmatter relates-to: arrays
  • reference-for: pointers in <slug>/references/<name>.md files

Examples:

  • [[orders-pipeline/workitem|orders-pipeline]]
  • [[pdf-archive-pipeline/workitem|pdf-archive-pipeline]]
  • [[recipe-tag-taxonomy/workitem|recipe-tag-taxonomy]]

In multi-root mode the qualified form (see Multi-root slug naming below) composes naturally: [[packages-auth/auth-refactor/workitem|auth-refactor]].

Multi-root slug naming (DL #6 in crystal-multi-root)

When multiple roots resolve, slugs are qualified with the path segment immediately above tasks/ to prevent collisions across roots:

Single-root Multi-root
auth-refactor packages-auth/auth-refactor
billing-rewrite apps-billing/billing-rewrite

The qualifier comes from basename(dirname(<root>)). In commands that accept a slug (crystal-cut <slug>, crystal-cave <slug>), pass the qualified form when multi-root is active.

On first crystal-grow in a project without the root, create it silently (mkdir -p — no confirmation prompt, per DL #20). When paths is set explicitly, create only roots that match the glob.

Slug collision policy (DL #24)

Before creating, check both layouts:

  1. <root>/<slug>/ exists → refuse: "Crystal <slug> already exists at <path>. Use a different slug or open the existing one with /vdm:crystal-cave."

  2. <root>/<slug>.md exists (flat file) → refuse with hint:

    × Collision: <root>/<slug>.md уже существует (flat файл).
      Crystal требует folder structure. Варианты:
        - Использовать другой slug
        - Вручную переименовать flat файл, затем повторить grow
        - (v2) Auto-convert flat → folder — отложено
    
  3. Neither → proceed with creation.

Migrating legacy docs (onboarding)

Installing the suite into a repo that already has a pile of old PRDs / task notes is the most common first-run scenario. crystal-grow is not the migration tool — it refuses on collision by design (DL #24). Migration is a manual, judgment-driven move you do once per legacy file, not a command.

Pick one of three paths per file:

Path When How
move-to-references The doc is finished/historical but its content is worth keeping verbatim (specs, hand-offs, prior reasoning). git mv <root>/<slug>.md <root>/<slug>/references/<original>.md, then hand-write a fresh workitem.md on top that summarizes and [[links]] the reference. Mirrors how this suite's own design crystal keeps references/original-spec.md.
inline-rewrite The doc is the workitem, just in a pre-crystal format. Create <root>/<slug>/, write workitem.md adapting the old content into the schema (frontmatter + ## Next actions + ## Sidetracks). Carry every open - [ ] across unchanged. git rm the flat original once content is transferred.
delete Stale / obsolete / superseded. Just remove it. Not every old doc earns a migration.

Two pitfalls when choosing the path:

  • Don't carry doc-type filenames into slugs. If the legacy filename is a doc-type tag (PRD.md, TODO.md, SPEC.md, NOTES.md), it carries no content signal — rename to a descriptive content slug based on what the work does, per the project's domain. Example: manual-pipeline/PRD.mdpdf-archive-pipeline/workitem.md, not PRD/workitem.md. Slug stays kebab-case lowercase regardless (so PRD would double-violate: caps and meaningless).
  • Brainstorm transcripts → references/, not sibling workitems. Brainstorm transcripts, design-discussion transcripts, and historical decision-log artifacts often look like workitems by structure (they may even contain a ## Decision Log) but they're frozen content with no future obligations. Workitem = unit with future actions. Reference = frozen content, no own future. Default to move-to-references under the parent workitem (<parent>/references/<name>.md), not inline-rewrite as a sibling — unless the doc genuinely defines ongoing work.

Two rules that bite during migration:

  • Dates reflect real history, not the import moment (the agent's P4). Set created: to the doc's first commit date and last-updated: to its last real touch — e.g. git log --diff-filter=A --format=%as -- <file> for created, git log -1 --format=%as -- <file> for last-updated. The cave's recency view is only honest if it shows when work actually happened, not when you ran the migration.
  • Importing an already-complete doc means writing it at status: done — which must contain zero - [ ]. The completion-guard fires on the creating Write too (see crystal-cut → Gate behavior). Convert any leftover unchecked boxes to [x] or one of the five resolution states before the Write, or the gate blocks the import.

Behavioral protocol

When /vdm:crystal-grow [slug] is invoked (or auto-promotion is accepted):

Step 1: Classify session

If session-type isn't supplied, infer from the conversation so far. Use the assistant's best judgment from the first few messages; default to other when unclear.

Step 2: Resolve root and check collisions

Run resolve_crystal_roots. The behavior depends on how many roots resolve:

One root (single-root mode, classic): use that root, run the collision check below, proceed.

Multiple roots (multi-root mode, monorepo/vault): select target by CWD confidence, fall back to HITL when ambiguous (DL #7 in crystal-multi-root):

CWD signal Confidence Action
pwd is under exactly one resolved root high grow into that root silently
pwd is the parent of exactly one <x>/tasks/ high grow into <x>/tasks/ silently
pwd is project root with ≥2 roots resolved low HITL — ask which root: «Куда заводим? //
pwd is outside all resolved roots low HITL — same question, with resolved roots listed
slug argument already contains <root-qualifier>/ prefix high use that root, slug = portion after /

Then run the collision check below in the selected root. Abort with the appropriate message on conflict.

Step 3: Create the workitem

Read the template at ${CLAUDE_PLUGIN_ROOT}/templates/workitem-template.md, substitute placeholders, write to <root>/<slug>/workitem.md. Frontmatter:

---
title: "<human title>"
slug: <kebab>
description: "<one-liner for cave/base overview>"  # optional
status: in-progress
session-type: <type>
created: <YYYY-MM-DD>
last-updated: <YYYY-MM-DD>
---

When session-type is brainstorm or prd-prep, the template should include the ## Decision Log section by default (Decision Log #13).

Step 4: Seed from shadow

Any sidetracks the assistant has been carrying in shadow mode go into ## Sidetracks as numbered ### #N. ... entries with Status: open and their Возникло в: provenance (Decision Log #5).

If promoting from shadow, the optional description: field is derived from the shadow's tracked session intent (the one-line characterization the assistant used to classify session-type). For explicit grow with a slug argument, derive from the immediate invocation context or leave commented in the template — the field is optional, blank is fine, but filling it materially improves the cave/base overview signal-to-noise when the workitem accumulates siblings. Keep it ≤80 chars, describe what the work does, not what it is (verb-leaning, not noun-leaning).

Step 5: Populate Tasks (TaskCreate)

For each unchecked item in ## Next actions and each open sidetrack, call TaskCreate so the user sees the workitem mirrored as ephemeral visualization (Decision Log #21). The file remains source of truth — UI ticks do not propagate back. Make this contract explicit when handing off:

✓ Crystal <slug> создан. N задач помещены в Tasks как visualization; для resolve правьте <root>/<slug>/workitem.md (file = source of truth).

Step 6: Announce

One-line confirmation with the path so the user can open it immediately:

🌱 Crystal <slug> создан: <root>/<slug>/workitem.md.

Session resume

When a session starts, the SessionStart hook (${CLAUDE_PLUGIN_ROOT}/scripts/crystal-hydrate.sh) lists active workitems. On invocation, Read the listed workitem(s) before continuing if relevant to this session. If the SessionStart hook didn't fire (disabled / replaced / older harness), do the same Read step manually as a fallback (Decision Log #23, Layer 2).

After context compaction: if an active crystal was in scope before the compaction summary, re-Read its workitem.md before continuing.

Configuration sub-commands

/vdm:crystal-grow [subcommand] recognizes these as the first word of arguments. When no subcommand matches, behave as the regular grow skill described above. Full surface in v1 (DL #11 in crystal-multi-root).

Enable / disable / reset

Subcommand Effect on .claude/vdm-plugins.jsoncrystal
off / disable Set enabled = false (hooks stay silent)
on / enable Set enabled = true
reset Remove the crystal key (revert to defaults)
config / status Diagnostic: resolved roots + derived singleton + per-tier counts + non-canonical drift

Root paths (multi-root, DL #11)

Subcommand Effect
paths add <glob> Append glob to paths; idempotent; warns if glob matches 0 dirs but still writes
paths remove <glob> Remove glob; warns if not found; empties → key deleted (falls back to path or auto-scan)
paths list Pretty-print current paths + resolved roots; empty → "auto-scan active"
paths set <g1> <g2> ... Replace entire array (space-separated, supports quoted multi-word globs)
paths clear Remove the paths key
path <value> Legacy single-root; refuses if paths is set (force paths clear first or edit JSON manually)
path clear Remove the path key (migration helper)

Status aliases (project-specific vocabulary mapped to canonical, DL #10)

Subcommand Effect
status-alias add <from>=<to> Map <from><to>; validates <to> against canonical taxonomy, errors on unknown
status-alias remove <from> Unmap; warns if not found
status-alias list Pretty-print current aliases; empty → "no aliases configured"
status-alias clear Remove the status-aliases key

Singleton invariant override

Subcommand Effect
singleton global Exactly 1 active workitem repo-wide
singleton per-root Exactly 1 active per resolved root
singleton off No invariant; explicit user override
singleton auto Remove key → derive from #roots (1 → global, ≥2 → per-root)

Mixed-config policy

Setting path while paths is already set is refused with an explicit error rather than written-and-warned (footgun avoidance). Same in reverse: paths overrides path and emits a warning at SessionStart hook time. To migrate from singular to plural: path clear then paths add <glob>.

Defaults

When the crystal section is absent entirely: enabled: true. Everything else flows from defaults:

  • Roots: auto-scan from project root.
  • Singleton: derived from #roots (global if 1, per-root if ≥2).
  • Status-aliases: none.

This means a fresh install with no config works correctly for both classic single-root (docs/tasks/) and monorepo/vault (packages/*/tasks/) layouts with zero ceremony.

Implementation rules (patching)

  1. Read the file (if missing, start with {}).
  2. Modify only the crystal key — preserve learn, docs-sync, changelog, git-guard verbatim.
  3. For reset, delete the crystal key (do not leave "crystal": {}).
  4. Use the Edit/Write tool — do not invoke jq; users may not have it.
  5. Final file must be valid JSON, 2-space indent, trailing newline.
  6. Validate status-alias add's <to> value against the canonical taxonomy (crystal_canonical_statuses in lib/crystal-path.sh).

The same crystal config section is shared by all four crystal-* skills and by the hook scripts (completion-guard, stop-reminder, hydrate). One section, one source of truth.

Config file path detection

  1. project_root = git rev-parse --show-toplevel (fallback: pwd)
  2. If <project_root>/.claude/ exists → <project_root>/.claude/vdm-plugins.json
  3. Else if <project_root>/.qwen/ exists → <project_root>/.qwen/vdm-plugins.json
  4. Else create <project_root>/.claude/ and write there.

Examples

Example 1: Promotion from shadow

User: "Слушай, мы уже час крутимся вокруг auth refactor, и я вижу что у тебя накопились побеги. Давай заведём crystal."

Assistant flow:

  1. Classify session as research (already past threshold — 4+ tool calls).
  2. Resolve root → docs/tasks/. Check collisions for slug auth-refactor — none.
  3. Write docs/tasks/auth-refactor/workitem.md from template with the 3 shadow sidetracks already captured.
  4. TaskCreate for each: 2 open sidetracks + the 1 unchecked "Next action."
  5. Announce: 🌱 Crystal auth-refactor создан: docs/tasks/auth-refactor/workitem.md.

Example 2: Direct grow with explicit slug

User: /vdm:crystal-grow stripe-webhook-rewrite

Assistant flow:

  1. session-type inference from the conversation so far.
  2. Check docs/tasks/stripe-webhook-rewrite/ — does not exist.
  3. Write the workitem with empty ## Sidetracks, seeded ## Next actions from the user's stated goal.
  4. TaskCreate as in Example 1.

Example 3: Configurable path

User: /vdm:crystal-grow path tasks

Assistant updates .claude/vdm-plugins.json:

{
  "crystal": {
    "path": "tasks"
  }
}

Future crystal-grow / -bud / -cut / -cave and all hook scripts resolve to <project_root>/tasks/ instead of the default docs/tasks/.

Quality gates

A successful grow ends in this state:

  • <root>/<slug>/workitem.md exists with valid frontmatter
  • status: in-progress
  • Singleton invariant respected: any other in-progress workitems were explicitly switched to dormant first (DL #11)
  • TaskCreate has populated Tasks UI from the workitem's unchecked items
  • User received the one-line confirmation with the path

Integration with other skills

Other skill Interaction
crystal-bud Append sidetracks to this workitem (or routed dormant)
crystal-cut Close this workitem when all - [ ] are addressed
crystal-cave View this workitem + sidetracks + decision log
/vdm:docs-sync Phase 0 sweep surfaces this workitem if open
/vdm:changelog Soft-hinted by crystal-cut after a successful close
/vdm:learn Soft-hinted by crystal-cut when sidetracks were resolved

Reflexive case

The first crystal in this repo is docs/tasks/crystal-design/workitem.md itself — the design document for this very suite was promoted to a full crystal mid-brainstorm (Decision Log #17). Read it for a worked example of the format and conventions; the structure described in this skill is the structure that file follows.

Install via CLI
npx skills add https://github.com/VoDmAl/ai-dev-plugins --skill crystal-grow
Repository Details
star Stars 0
call_split Forks 0
navigation Branch main
article Path SKILL.md
More from Creator