8.7 KiB
8.7 KiB
Issue #824 — Mosaic skill CLI and Claude bridge auto-sync
Objective
Deliver mosaic skill register|unregister|list plus install/upgrade reconciliation of every canonical ~/.config/mosaic/skills/* entry into ~/.claude/skills/, without clobbering runtime-owned files or directories.
Scope and constraints
- Issue: mosaic skill management CLI — register/unregister + symlink-bridge auto-sync on install/upgrade (mosaicstack/stack#824)
- Branch:
feat/824-mosaic-skill-cli - M1 runtime: Claude Code only.
- Pi/Codex parity is documentation-only; no non-Claude bridge implementation.
- Do not author the downstream
mosaic-context-refreshskill. - Workers do not modify
docs/TASKS.md, merge, close #824, or touchmain. - TDD is mandatory and red-first; filesystem tests use temporary directories only.
- Budget: no explicit token cap supplied; use a focused single-worker implementation with no new dependencies.
Requirements mapping
- Register creates the canonical Claude symlink and is idempotent.
- Names are untrusted: reject empty/escaping/absolute/separator/
../leading-dash names before filesystem mutation, with clear CLI stderr and nonzero status. - Register repairs only Mosaic-owned dangling symlinks and refuses foreign files, directories, and symlinks.
- Unregister removes only symlinks pointing inside the canonical Mosaic skills root and is idempotent when absent.
- List reports registered, dangling, foreign, and canonical-but-unregistered skills.
- Install and upgrade generically reconcile all canonical skills after framework sync/re-seed, continuing past foreign conflicts without clobbering them.
- User/developer documentation describes commands, status meanings, security boundaries, and Claude-only M1 scope.
Plan
- Add co-located failing Vitest coverage for all filesystem behaviors and auto-sync.
- Run the focused spec and record the expected RED failure.
- Commit the red contract as
test(#824): .... - Implement the skill bridge and Commander command registration.
- Wire reconciliation into wizard finalize and
mosaic updatere-seed, preserving non-clobber behavior. - Update canonical docs and sitemap if navigation changes.
- Run focused tests, package tests, typecheck, lint, and formatting.
- Commit implementation/docs as
feat(#824): ..., queue-guard, push, open PR withCloses #824., fire completion event, and notify the coordinator.
Progress
- 2026-07-17: Loaded mission/delivery/TDD/documentation rails, issue #824, active mission state, and relevant installer/update paths.
- 2026-07-17: Confirmed
mosaic updateinvokesframework/install.shwithMOSAIC_SYNC_ONLY=1; that path exits before existing post-install skill linking, leaving newly present canonical skills unregistered. - 2026-07-17: Coordinator addendum classified the user-supplied skill name and runtime symlink target as a path-traversal/symlink-injection surface. Expanded the initial red contract to reject traversal before mutation, preserve every foreign entry, and unregister Mosaic-owned links only.
- 2026-07-17: Implemented the Commander command group and secure generic bridge; wired wizard finalize and successful framework re-seed reconciliation; updated user/developer/installed/root docs and sitemap.
- 2026-07-17: Focused, package-wide, repository baseline, temp-home situational, and independent review gates completed. Ready for scoped feature commit, queue guard, push, and PR handoff.
Tests and evidence
TDD evidence
- RED environment attempt:
pnpm --filter @mosaicstack/mosaic exec vitest run src/commands/skill.spec.tsinitially could not locate Vitest because this fresh worktree had no dependencies. - Dependency setup:
pnpm install --frozen-lockfile --store-dir /home/hermes/.local/share/pnpm/storesucceeded. The explicit store was required because machine pnpm config incorrectly resolves the default store under/root. - RED behavior: focused Vitest failed with
Failed to load url ./skill.js ... Does the file exist?, proving the bridge API was absent. - RED integration: finalize/update specs failed because no Claude links or
skillSyncresult existed. - RED symlink injection: symlinked Claude/canonical root tests failed because the initial implementation followed ancestor links.
- GREEN after review remediation:
skill.spec.ts36/36,finalize-skills.spec.ts6/6, andupdate-checker.reseed.spec.ts30/30.
Baseline gates
pnpm --filter '@mosaicstack/mosaic...' run build— pass (fresh-worktree dependency outputs built).pnpm --filter @mosaicstack/mosaic run typecheck— pass.pnpm --filter @mosaicstack/mosaic run lint— pass.pnpm --filter @mosaicstack/mosaic test— pass: 69 files, 1,325 Vitest tests plus framework shell suite.pnpm typecheck— pass: 42/42 Turbo tasks.pnpm lint— pass: 23/23 Turbo tasks.pnpm format:check— pass.
Situational evidence
A built-CLI temp-home smoke test (no real ~/.claude or Mosaic config touched) proved:
- register creates the exact link and a second run reports
already registered; - list reports registered and unregistered canonical skills;
../../etcexits 1 withInvalid skill nameand creates no escaped path;- unregister removes the managed link and a second run reports
already unregistered; - a fake successful framework re-seed generically registered both
added-after-setupandsecond-skillfrom runtime directory enumeration.
Review evidence
- Initial uncommitted Codex code/security review described name validation/clobber protection as strong; its only finding was the harness-owned, unrelated
.mosaic/orchestrator/session.lock, which is excluded from all commits and the PR. - Exact branch review then identified two remediations: preserve successful framework re-seed status when bridge-wide reconciliation fails, and reject/escape control-character names to prevent terminal/log injection.
- Both findings were reproduced red-first and remediated. A subsequent exact review identified one finalize failure-isolation blocker; a root-wide bridge error now warns and allows wizard doctor/summary/next-steps completion, with a red-first regression.
- All remediations passed the full package and repository gates. Final exact-head review is rerun after amending the feature commit.
Acceptance mapping
| Acceptance criterion | Evidence |
|---|---|
| register/unregister/list, idempotent | skill.spec.ts and built-CLI temp-home smoke |
| traversal/symlink-injection protection | invalid-name matrix, foreign file/dir/link tests, symlinked-root tests |
| list flags dangling and foreign entries | deterministic list status test |
| install and upgrade auto-sync every canonical directory | finalize + framework re-seed integration specs; two-skill built-module smoke |
| newly added skill becomes discoverable without manual link | added-after-setup auto-sync creates exact Claude link; Claude can rescan with /reload-skills or a new session |
| Pi/Codex parity captured as scope note | user guide, developer guide, installed framework README |
| documentation gate | root README, user guide, developer guide, framework README, sitemap |
Risks
- Symlink replacement uses
lstatsemantics so dangling links are detectable without following them. - Link ownership is determined lexically against the canonical skills root, and existing symlink ancestors in either managed root are rejected before mutation.
- Auto-sync continues across per-skill conflicts while never deleting real files/directories or foreign symlinks.
- Claude Code discovers filesystem skills at session launch/reload boundaries; bridge creation makes a later
/reload-skillsor new session able to discover the skill, but cannot mutate an already-cached in-process registry by itself. - Pi does not need this Claude bridge because its Mosaic launcher can consume the canonical root. Codex lifecycle parity remains explicitly deferred.
- No deployment surface is affected.
PR #826 review remediation
- 2026-07-17: Exact-head RoR requested changes for two ownership bugs: installer pruning deleted foreign-name links under
MOSAIC_HOMEoutside canonical skills, and unregister deleted a same-root link targeting a different skill. It also requested trailing-dot rejection and executable coverage support. - RED evidence: focused regression run failed 4 tests: register/unregister accepted
safe., misdirected unregister did not throw, and the install linker deleted the foreign-name link. - GREEN evidence:
skill.spec.tspasses 43/43, including live and dangling foreign-name links in a temp HOME/MOSAIC_HOME and the misdirected unregister invariant. - Coverage:
vitest run src/commands/skill.spec.ts --coveragepasses configured 85% thresholds forskill.ts: 91.05% statements/lines, 86.27% branches, 95.23% functions. - Full gates: package build passed; package tests passed 69 files / 1,332 tests plus framework shell suite; repository typecheck 42/42, lint 23/23, and format check passed.