Cktovd
A daily-driver collection of skills for agentic coding — a portable, agent-agnostic catalog managed with the vd CLI.
npx -y skills add vanducng/skills --skill cktovdAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 2 stars2 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Migrate from claudekit (ck) to the vd-cli control plane - install clean-room hooks, convert .ck.json to .vd.json, audit CK_*→VD_* env consumers, and move a repo's plans/ artifacts into feature-first .workbench folders under <git-root>/.workbench/features/<feature>/{plans,reports,journals,visuals,state}. Use when the user says 'cktovd', 'migrate to vd', 'migrate plans to .workbench', 'enable the .workbench umbrella', or 'switch this repo off claudekit'.
The file declares its own license as MIT. That is the author’s claim about this one file, and it is not the same thing as the license GitHub reports for the repository, which is listed with the other numbers below.
SKILL.md
11.4 KB, as published. Nobody here has run it
cktovd - claudekit → vd-cli migration
Migrate two layers, in order:
- Machine (once): vd-cli owns the Claude Code control plane - clean-room hooks in
~/.claude/hooks/, config in~/.claude/.vd.json. - Repo (per project): opt into the feature-first
.workbenchumbrella via local<git-root>/.vd.jsonand physically moveplans/artifacts into per-feature folders.
Detect scope from the argument: --machine → machine layer only; a repo path (default: cwd git root) → repo layer (runs a machine preflight first); --check → verify only, change nothing.
Layout contract (source of truth: vd-cli hooks/lib/vd_paths.py)
| Artifact | Legacy (no umbrella) | Feature-first .workbench |
|---|---|---|
| Plans | <cwd>/plans/ | <git-root>/.workbench/features/<feature>/plans/ |
| Reports | plans/reports/ | <git-root>/.workbench/features/<feature>/reports/ |
| Journals | plans/journals/ | <git-root>/.workbench/features/<feature>/journals/ |
| Visuals | plans/visuals/ | <git-root>/.workbench/features/<feature>/visuals/ |
| State | plans/goals/ | <git-root>/.workbench/features/<feature>/state/ (renamed: goals → state) |
| Docs | <cwd>/docs/ | <cwd>/docs/ - never moves: git-tracked team deliverables, umbrella-blind by design |
The umbrella dir is named tool-neutrally (.workbench, not .vd) because multiple agents share the repo. Git worktrees are a separate top-level <git-root>/.worktrees/ (not under .workbench); artifacts written from inside a worktree resolve to the main repo's .workbench/ (they survive git worktree remove), while docs/ stays branch-local.
Config precedence: DEFAULT ← global ~/.claude/.vd.json ← project <git-root>/.vd.json. There is no .ck.json read fallback (removed): vd reads .vd.json only. A lingering .ck.json without its .vd.json is no longer silently honored - it raises a migration error ("run the cktovd skill / rename to .vd.json"). So the cutover is: create the .vd.json (from .ck.json if present). Once .vd.json exists the old file is inert (ignored); it's safe to delete - the master backup is your real safety net. vd never writes .ck.json.
paths.umbrella must be a relative path with no ../empty segments that resolves inside the git root. An invalid value (absolute, traversal, empty) never errors - it's silently coerced to null, which restores the byte-identical legacy layout, so a bad value presents as "migration didn't take effect". A plausible typo (.wrok) passes sanitization and creates a wrong-named umbrella dir. Umbrella resolution anchors via git rev-parse --show-toplevel - a non-git dir never activates it; git init first.
Machine layer
- Master backup (the cutover is destructive; the full-restore recipe depends on this):
ts=$(date +%Y%m%dT%H%M%S); bak=~/.claude-ck-removal-backup-$ts mkdir -p "$bak" && cp -Rp ~/.claude/hooks "$bak"/ && cp ~/.claude/settings.json "$bak"/ cp ~/.claude/.ck.json "$bak"/ 2>/dev/null || true - Preflight:
vd --version(hooks support landed in 2.4.0; the.vd.jsonrename is post-2.5.0 -brew upgrade vdif older). Never patch deployed hooks in place - ck'sck updateclobbers its files, and vd's are redeployed byvd install hooks. - Cut over:
npm uninstall -g claudekit-cli vd install hooks grep -n '\$HOME/.claude/hooks' ~/.claude/settings.json # all entries must show literal $HOMEvd install hooksis idempotent: byte-identical files are skipped, a differing vd-owned file is backed up once as<name>.bak.<UTC-ts>.<ext>, unknown files are never touched. settings.json edits are surgical (only vd's hook entries + the top-levelstatusLinekey are patched; a one-timesettings.json.bakis taken first). Registered commands keep$HOMEliteral - the hook runner shell-expands it; never bake a personal absolute path in. Caution: registration matches existing entries by filename substring and rewrites them to vd's canonical command - a custom wrapper referencing the same filename gets clobbered. - Config rename (required - prevents the migration error):
[ -f ~/.claude/.ck.json ] && [ ! -f ~/.claude/.vd.json ] && cp ~/.claude/.ck.json ~/.claude/.vd.json. With the fallback gone, a global~/.claude/.ck.jsonwithout~/.claude/.vd.jsonerrors on every session, so thiscpis mandatory when a legacy global config exists. After.vd.jsonexists,rm ~/.claude/.ck.json(it's inert and backed up). Do not setpaths.umbrellaglobally - that flips every repo at once and strands un-migratedplans/content. Go per-repo; consider the global flip only after all active repos are migrated. - CK_ consumer audit* - the env rename is a hard cut: every
CK_*session var has aVD_*successor (VD_REPORTS_PATH,VD_PLANS_PATH,VD_GIT_ROOT,VD_ACTIVE_PLAN, …) and anything still readingCK_*silently gets nothing. Temp files renamedck-session-*→vd-session-*.
When fixing hits, work from an explicit inventory of your variable names - a blanketgrep -rn 'CK_' ~/.claude/ --include='*.cjs' --include='*.json' --include='*.sh' \ | grep -v node_modules | grep -v '.bak'CK_→VD_replace hits substring false-positives (SLACK_WEBHOOK_URL,BLOCK_LOW_AND_ABOVE, …) and third-party vars.
Repo layer
- Opt in at the git root. Default to local-only repo config unless the team explicitly wants to share it:
cd "$(git rev-parse --show-toplevel)" printf '{\n "paths": {\n "umbrella": ".workbench",\n "layout": "feature-first"\n }\n}\n' > .vd.json grep -qxF '.vd.json' .gitignore 2>/dev/null || printf '\n.vd.json\n' >> .gitignore - Move artifacts into one feature folder per ticket/topic. Prefer
vd:workbench new <slug> --ticket <ticket>to createfeature.json, then move legacy files into that feature's type folders:
Missing sources are fine - absent subdirs appear lazily on first use. If the current session is writing into a plan dir, move that dir last (or from a copy). Follow-ups that bite: rewrite embeddedpython3 ~/.claude/skills/workbench/scripts/workbench.py new my-feature --ticket PROJ-123 feature=.workbench/features/proj-123-my-feature mkdir -p "$feature"/{plans,reports,journals,visuals,state} [ -d plans/reports ] && find plans/reports -mindepth 1 -maxdepth 1 -exec mv {} "$feature/reports/" \; [ -d plans/journals ] && find plans/journals -mindepth 1 -maxdepth 1 -exec mv {} "$feature/journals/" \; [ -d plans/visuals ] && find plans/visuals -mindepth 1 -maxdepth 1 -exec mv {} "$feature/visuals/" \; [ -d plans/goals ] && find plans/goals -mindepth 1 -maxdepth 1 -exec mv {} "$feature/state/" \; find plans -mindepth 1 -maxdepth 1 -type d -exec mv {} "$feature/plans/" \; 2>/dev/null rmdir plansplans/goalspaths inside moved state files;git ls-files .workbench/ | xargs -r git rm -r --cachedfor anything previously tracked underplans/(amvinto an ignored dir doesn't untrack it). - Gitignore: replace the
plansentry with.workbench. Don't keep both - if a non-migrated tool recreatesplans/, it should show up untracked as a signal..vd.jsonstays local-only via.gitignoreunless the team explicitly chooses a shared config. - Stale references: grep the repo (
CLAUDE.md,AGENTS.md,docs/,.github/, scripts) for hardcodedplans/paths and point them at the hook-injected paths instead.
Already on the legacy .work umbrella? A repo migrated before the .workbench rename just needs the dir + config flipped: git mv .work .workbench (or mv if untracked), set paths.umbrella to .workbench in .vd.json, and swap the .gitignore entry .work/ → .workbench/. The hook re-resolves to .workbench/ on the next prompt; legacy .work/ is still recognized read-only by the resume probes during the transition.
Verify (also the whole of --check)
Run from inside the target repo - loadConfig() resolves the project .vd.json from process.cwd(); the payload cwd only anchors path strings, so piping from elsewhere false-negatives into legacy output.
cd "$(git rev-parse --show-toplevel)"
git check-ignore -v .workbench/features/example/reports/x && echo "OK .workbench ignored"
git check-ignore -v docs/x; [ $? -eq 1 ] && echo "OK docs NOT ignored"
echo "{\"cwd\":\"$PWD\",\"session_id\":\"check\"}" | python3 ~/.claude/hooks/dev-rules-reminder.py
Pass = the injected ## Paths block shows six paths (Reports/Plans/Docs/Visuals/Journals/State) with everything except Docs under .workbench/. Three paths = umbrella did not activate (invalid umbrella value, missing .vd.json, or you ran from outside the repo). A malformed .vd.json doesn't crash - it silently falls back to the 3-path legacy injection, so assert on output, never trust the config edit. No paths at all = a legacy .ck.json lingers without a .vd.json: the loader now raises a migration error and the fail-open hook injects nothing - create the .vd.json (step 4 / repo step 1). New paths take effect on the next prompt; the current session's earlier injection still shows old paths - don't chase that as a bug.
Rollback
- Repo: delete
<git-root>/.vd.json(or set umbrella null) and reverse the gitignore swap → legacy layout resumes byte-identically;mvthe.workbenchtrees back if needed. - Machine:
vd hooks rollback(restore newest*.bak.*+settings.json.bak) orvd hooks uninstall(remove + unregister vd-managed files only; third-party hooks untouched). Both support--dry-run. Caution: the uninstall allowlist can lag the deployed asset set - check--dry-runoutput and remove orphans by hand. - Full restore: copy the master backup back over
~/.claude/hooks+settings.json, thennpm i -g claudekit-cli.
Gotchas
- Reports under feature-first live in the feature root, not inside a plan dir:
features/<feature>/reports, alongsidefeatures/<feature>/plans. Anything string-concatenatingplans/reports/...breaks; always use the hook-injectedReports:path. task-completed-handler.py/teammate-idle-handler.pylive in~/.claude/hooksbut are deliberately not in settings.json (team-runtime invoked) - an "unregistered hook" finding there is not a bug; don't register or delete them.- Subagent prompts must pass the work-context repo's
.workbench/paths, not the cwd's, when editing another project. - Monorepos: the umbrella anchors to the git root, not cwd - one
.vd.jsonand one.workbench/per repo, even when working from a subdirectory. - Dotfiles repos at
$HOME: stray-home protection anchors child projects to themselves by default; setpaths.allowHomeRoot: trueonly when$HOMEis intentionally the artifact root. - A corrupted ancestor
.git/config(e.g. a stray repo at$HOME) silently disables git-root resolution for loose dirs beneath it - umbrella just stays off, no error.