v0.27.2
Shell self-sync skill wipe + the health oracle that certified it healthy.
THE BUG: syncProject copies skills by rm'ing each destination skill and then copying the source over it. When projectRoot === shellRoot they are THE SAME DIRECTORY — the rm destroys the source, the copy finds nothing, and the whole thing exits 0 reporting "Synced 0 file(s)". Every skill, gone, silently. A clean-machine UAT lost all 17 distributable skills this way, and rks_preflight reported 7/7 green throughout, because not one of its checks looked at .claude/skills. rks could not route a single thing — no /build, no /ship, no /research — and the health check said it was fine.
REACHABLE FROM FOUR CALLERS, because setup.mjs registers the SHELL in its own registry and nothing told them a shell is not one of its own children: project sync --all, project upgrade --all, routekit doctor, and bootstrap.
-
THE PRIMITIVE: syncProject and bootstrap now refuse a self-targeted sync, compared by filesystem IDENTITY (device + inode) — not by string, which is defeated by a trailing slash, a
..segment, a symlink, and a case-variant on a case-insensitive volume; and not by realpath, which still cannot see two different real paths sharing an inode. The guard fires on identity, NEVER on containment: a legitimate child nested inside the shell directory syncs normally. A second guard sits at the point of destruction, because a child's skills dir can be a symlink back to the shell's and the entry guard would never see it. -
THE CALLERS:
sync --all,upgrade --allanddoctorskip the shell's own registry record. doctor skips at the top of the loop — repinMcpServer and migrateConfig run there too, and migrateConfig writes the shell's own project.json. -
THE HEALTH ORACLE: rks_preflight gains a core_skills check, manifest-driven. It also stops reporting a DETACHED HEAD as healthy —
git rev-parse --abbrev-ref HEADSUCCEEDS when detached and returns the literal string "HEAD", so the old!!currentBranchfallback was truthy. That is the state README's own "pin to a tag for stability" advice puts you in. -
THE MANIFEST: .routekit/skills-manifest.json is now the one source of truth for the skill inventory, replacing two separately-hardcoded SKILLS_EXCLUDE sets. A test holds it to the filesystem in both directions. It ships to the public mirror — without that, the new check would silently no-op on a mirror clone, which is exactly the machine the wipe was found on.
-
SILENCE IS A BUG: a missing skills source is now LOUD (a shell with no skills to give cannot bootstrap or repair a child); missing OPTIONAL sources (hooks/prompts/agents) stay tolerated. setup.mjs gains a health gate that names a detached HEAD and restores missing skills FROM GIT — never from syncProject, which is the thing that deletes them.
Also: setup's dirty-tree branch-landing skip is no longer a neutral aside — on the UAT box the tree was dirty BECAUSE the skills were deleted, so the one signal that could have surfaced the problem was suppressed by it.