From 1556982dbc17013d22204081020aa89d3fce33fd Mon Sep 17 00:00:00 2001 From: fargo Date: Wed, 19 Aug 2026 14:39:28 -0500 Subject: [PATCH] =?UTF-8?q?skills:=20single=20install=20path=20=E2=80=94?= =?UTF-8?q?=20canonical=20skills=20ship=20with=20the=20framework?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Phase D2 of plan 2026-08-19: single package, single install, single command. The framework installer already treats skills/** as a shipped, manifest-owned framework subtree, so the folded skills now install into $MOSAIC_HOME/skills with the rest of the framework — no second repository, no separate sync step: - mosaic-sync-skills (bash + powershell): the fetch machinery is gone (clone, pull, dirty-state migration, rsync from sources/agent-skills). The script now only links installed skills into runtime homes. --link-only is a compat no-op; --no-link exits having nothing to do. - catalog.ts: the sources/agent-skills fallback is dead and removed. - install.sh, launch.ts, defaults/README.md, README.md, skills/README.md: references to the second repo rewritten to describe the shipped path. Verified: clean install into a fresh MOSAIC_HOME produces 102 skills with no sources/ directory; the linker then links the selected skills into the four runtime homes with no git involvement. --- README.md | 2 +- packages/mosaic/framework/defaults/README.md | 8 +- packages/mosaic/framework/install.sh | 4 +- packages/mosaic/framework/skills/README.md | 32 ++--- .../tools/_scripts/mosaic-sync-skills | 123 +++--------------- .../tools/_scripts/mosaic-sync-skills.ps1 | 47 ++----- packages/mosaic/src/commands/launch.ts | 2 +- packages/mosaic/src/platform/file-ops.ts | 3 +- packages/mosaic/src/skills/catalog.ts | 6 - 9 files changed, 55 insertions(+), 172 deletions(-) diff --git a/README.md b/README.md index 5fc7df21..b45c13ee 100644 --- a/README.md +++ b/README.md @@ -331,7 +331,7 @@ The framework is the bash-based standards layer installed to every developer mac ├── bin/mosaic ← Unified launcher (claude, codex, opencode, pi, yolo) ├── guides/ ← E2E delivery, orchestrator protocol, PRD, etc. ├── runtime/ ← Per-runtime configs (claude/, codex/, opencode/, pi/) -├── skills/ ← Universal skills (synced from agent-skills repo) +├── skills/ ← Universal skills (shipped with the framework package) ├── tools/ ← Tool suites (orchestrator, git, quality, prdy, etc.) └── memory/ ← Persistent agent memory (preserved across upgrades) ``` diff --git a/packages/mosaic/framework/defaults/README.md b/packages/mosaic/framework/defaults/README.md index 8222ffb3..873dbab9 100644 --- a/packages/mosaic/framework/defaults/README.md +++ b/packages/mosaic/framework/defaults/README.md @@ -126,7 +126,7 @@ You can still launch runtimes directly (`claude`, `codex`, etc.) — thin runtim │ ├── opencode/ ← AGENTS.md, RUNTIME.md │ ├── pi/ ← RUNTIME.md, mosaic-extension.ts │ └── mcp/ ← MCP server configs -├── skills/ ← Universal skills (synced from mosaic/agent-skills) +├── skills/ ← Universal skills (shipped with the framework package) ├── skills-local/ ← Local cross-runtime skills ├── memory/ ← Persistent agent memory (preserved across upgrades) └── templates/ ← SOUL.md template, project templates @@ -193,11 +193,11 @@ The installer rejects unrecognized flags or positional arguments before making c ## Universal Skills -The installer syncs skills from `mosaic/agent-skills` into `~/.config/mosaic/skills/`. Install, wizard finalization, and `mosaic update` automatically reconcile every canonical skill into Claude Code's `~/.claude/skills/` directory. +Canonical skills ship inside the framework package itself; the installer installs them into `~/.config/mosaic/skills/` together with the rest of the framework (there is no separate skills repository). Install, wizard finalization, and `mosaic update` automatically link every canonical skill into Claude Code's `~/.claude/skills/` directory. ```bash -mosaic sync # Full canonical catalog sync -~/.config/mosaic/tools/_scripts/mosaic-sync-skills --link-only # Re-link only +mosaic sync # Relink the full canonical catalog +~/.config/mosaic/tools/_scripts/mosaic-sync-skills --link-only # Re-link only (same as default) mosaic skill list # Show registered, missing, dangling, and foreign entries mosaic skill register # Register or repair one canonical Claude link mosaic skill unregister # Remove one Mosaic-owned Claude link diff --git a/packages/mosaic/framework/install.sh b/packages/mosaic/framework/install.sh index 1578c33b..1a5c8d64 100755 --- a/packages/mosaic/framework/install.sh +++ b/packages/mosaic/framework/install.sh @@ -17,7 +17,7 @@ set -Eeuo pipefail # MOSAIC_HOME — target directory (default: ~/.config/mosaic) # MOSAIC_INSTALL_MODE — prompt|keep|overwrite (default: prompt) # MOSAIC_ALLOW_MISSING_SEQUENTIAL_THINKING — 1 to bypass MCP check -# MOSAIC_SKIP_SKILLS_SYNC — 1 to skip skill sync +# MOSAIC_SKIP_SKILLS_SYNC — 1 to skip linking skills into runtime homes # # Flags (CLI args, NOT environment variables — see #869 Point-1 C2): # --allow-inactive-enforcement Explicit, per-invocation opt-out that lets the @@ -828,7 +828,7 @@ if [[ -x "$SCRIPTS/mosaic-ensure-excalidraw" ]]; then fi if [[ "${MOSAIC_SKIP_SKILLS_SYNC:-0}" != "1" ]] && [[ -x "$SCRIPTS/mosaic-sync-skills" ]]; then - "$SCRIPTS/mosaic-sync-skills" >/dev/null 2>&1 && ok "Skills synced" || warn "Skills sync failed (non-fatal)" + "$SCRIPTS/mosaic-sync-skills" >/dev/null 2>&1 && ok "Skills linked into runtime homes" || warn "Skills linking failed (non-fatal)" fi if [[ -x "$SCRIPTS/mosaic-migrate-local-skills" ]]; then diff --git a/packages/mosaic/framework/skills/README.md b/packages/mosaic/framework/skills/README.md index 6c2d1788..e796b233 100644 --- a/packages/mosaic/framework/skills/README.md +++ b/packages/mosaic/framework/skills/README.md @@ -1,6 +1,8 @@ # Agent Skills -Complete agent skill fleet for Mosaic Stack. 95 skills across 12 domains — coding, business development, design, marketing, writing, orchestration, document generation, Vue/Vite ecosystem, and more. Platform-aware — works with both GitHub (`gh`) and Gitea (`tea`) via our abstraction scripts. +Complete agent skill fleet for Mosaic Stack. 101 skills across 12 domains — coding, business development, design, marketing, writing, orchestration, document generation, Vue/Vite ecosystem, and more. Platform-aware — works with both GitHub (`gh`) and Gitea (`tea`) via our abstraction scripts. + +This tree lives in the monorepo (`packages/mosaic/framework/skills/`) and ships inside the framework package; it is no longer a separate repository. ## Security Audit @@ -196,29 +198,21 @@ All skills were reviewed on 2026-02-16. Findings: ## Installation -### Via npx (recommended) +The skills ship with the framework package. The framework installer installs them +into `~/.config/mosaic/skills/`, and the post-install step links them into each +runtime's skill directory: ```bash -# Install a single skill -npx skills add https://git.mosaicstack.dev/mosaic/agent-skills.git --skill nestjs-best-practices --agent claude-code +# Install or upgrade the framework (skills arrive with it — no second repo) +./packages/mosaic/framework/install.sh -# Install all skills -npx skills add https://git.mosaicstack.dev/mosaic/agent-skills.git --agent claude-code - -# Non-interactive (CI/scripting) -npx skills add https://git.mosaicstack.dev/mosaic/agent-skills.git --yes --agent claude-code +# Re-link installed skills into runtime homes (claude, codex, opencode, pi) +mosaic sync ``` -**Note:** The `.git` suffix on the URL is required for Gitea-hosted repos. - -### Manual (symlink from local clone) - -```bash -git clone https://git.mosaicstack.dev/mosaic/agent-skills.git ~/src/agent-skills -for skill in ~/src/agent-skills/skills/*/; do - ln -sf "$skill" ~/.config/mosaic/skills/$(basename "$skill") -done -``` +Operators can override any canonical skill by copying it to +`~/.config/mosaic/skills-local//` — local skills take precedence during +linking. ## Adapting Skills diff --git a/packages/mosaic/framework/tools/_scripts/mosaic-sync-skills b/packages/mosaic/framework/tools/_scripts/mosaic-sync-skills index e57aad17..0a75ea51 100755 --- a/packages/mosaic/framework/tools/_scripts/mosaic-sync-skills +++ b/packages/mosaic/framework/tools/_scripts/mosaic-sync-skills @@ -1,9 +1,14 @@ #!/usr/bin/env bash set -euo pipefail +# Link the INSTALLED canonical skills into runtime skill directories. +# +# The canonical skills ship inside the framework package itself +# (packages/mosaic/framework/skills in the monorepo) and are installed into +# $MOSAIC_HOME/skills by the framework installer — there is no second +# repository to clone. This script only maintains the runtime symlinks. + MOSAIC_HOME="${MOSAIC_HOME:-$HOME/.config/mosaic}" -SKILLS_REPO_URL="${MOSAIC_SKILLS_REPO_URL:-https://git.mosaicstack.dev/mosaic/agent-skills.git}" -SKILLS_REPO_DIR="${MOSAIC_SKILLS_REPO_DIR:-$MOSAIC_HOME/sources/agent-skills}" MOSAIC_SKILLS_DIR="$MOSAIC_HOME/skills" MOSAIC_LOCAL_SKILLS_DIR="$MOSAIC_HOME/skills-local" @@ -12,37 +17,34 @@ MOSAIC_LOCAL_SKILLS_DIR="$MOSAIC_HOME/skills-local" # (the legacy "mosaic sync" full-catalog behavior). MOSAIC_INSTALL_SKILLS="${MOSAIC_INSTALL_SKILLS:-}" -fetch=1 -link_only=0 - usage() { cat </dev/null || true)" - - if [[ -n "$dirty" ]]; then - # ── Auto-migrate customized skills to skills-local/ ───────────── - # Instead of stash/pop (fragile, merge conflicts), we: - # 1. Identify which skill dirs contain user edits - # 2. Copy those full skill dirs into skills-local/ (preserving edits) - # 3. Reset the repo clean so pull always succeeds - # 4. skills-local/ takes precedence during linking, so edits win - - SOURCE_SKILLS_SUBDIR="$SKILLS_REPO_DIR/skills" - migrated=() - - while IFS= read -r line; do - # porcelain format: XY — extract the file path - file="${line:3}" - # Only migrate files under skills/ subdir in the repo - if [[ "$file" == skills/* ]]; then - # Extract the skill directory name (first path component after skills/) - skill_name="${file#skills/}" - skill_name="${skill_name%%/*}" - - # Skip if already migrated this skill in this run - local_skill_dir="$MOSAIC_LOCAL_SKILLS_DIR/$skill_name" - if [[ -d "$local_skill_dir" ]]; then - continue - fi - - # Skip if skill_name is empty or hidden - if [[ -z "$skill_name" || "$skill_name" == .* ]]; then - continue - fi - - # Copy the skill (with user's edits) from repo working tree to skills-local/ - if [[ -d "$SOURCE_SKILLS_SUBDIR/$skill_name" ]]; then - cp -R "$SOURCE_SKILLS_SUBDIR/$skill_name" "$local_skill_dir" - migrated+=("$skill_name") - fi - fi - done <<< "$dirty" - - if [[ ${#migrated[@]} -gt 0 ]]; then - echo "[mosaic-skills] Migrated ${#migrated[@]} customized skill(s) to skills-local/:" - for s in "${migrated[@]}"; do - echo " → $MOSAIC_LOCAL_SKILLS_DIR/$s" - done - echo "[mosaic-skills] Your edits are preserved there and take precedence over canonical." - fi - - # Reset repo to clean state so pull always works - echo "[mosaic-skills] Resetting source repo to clean state..." - git -C "$SKILLS_REPO_DIR" checkout . 2>/dev/null || true - git -C "$SKILLS_REPO_DIR" clean -fd 2>/dev/null || true - fi - - if ! git -C "$SKILLS_REPO_DIR" pull --rebase 2>/dev/null; then - echo "[mosaic-skills] WARN: pull failed — continuing with existing checkout" >&2 - git -C "$SKILLS_REPO_DIR" rebase --abort 2>/dev/null || true - fi - else - echo "[mosaic-skills] Cloning skills source to: $SKILLS_REPO_DIR" - mkdir -p "$(dirname "$SKILLS_REPO_DIR")" - git clone "$SKILLS_REPO_URL" "$SKILLS_REPO_DIR" - fi - - SOURCE_SKILLS_DIR="$SKILLS_REPO_DIR/skills" - if [[ ! -d "$SOURCE_SKILLS_DIR" ]]; then - echo "[mosaic-skills] Missing source skills dir: $SOURCE_SKILLS_DIR" >&2 - exit 1 - fi - - if command -v rsync >/dev/null 2>&1; then - rsync -a --delete "$SOURCE_SKILLS_DIR/" "$MOSAIC_SKILLS_DIR/" - else - rm -rf "$MOSAIC_SKILLS_DIR"/* - cp -R "$SOURCE_SKILLS_DIR"/* "$MOSAIC_SKILLS_DIR"/ - fi -fi - if [[ ! -d "$MOSAIC_SKILLS_DIR" ]]; then echo "[mosaic-skills] Canonical skills dir missing: $MOSAIC_SKILLS_DIR" >&2 + echo "[mosaic-skills] Canonical skills ship with the framework — reinstall or update the framework package" >&2 exit 1 fi -if [[ $link_only -eq 1 ]]; then - echo "[mosaic-skills] Canonical sync completed (link update skipped)" - exit 0 -fi - # Skills are linked into the MOSAIC-OWNED harness homes, never a base install. # Paths mirror the config-dir env vars the launcher injects (HARNESS_HOME_ENV in # commands/launch.js): diff --git a/packages/mosaic/framework/tools/_scripts/mosaic-sync-skills.ps1 b/packages/mosaic/framework/tools/_scripts/mosaic-sync-skills.ps1 index 8bf30c5a..b60efffa 100644 --- a/packages/mosaic/framework/tools/_scripts/mosaic-sync-skills.ps1 +++ b/packages/mosaic/framework/tools/_scripts/mosaic-sync-skills.ps1 @@ -1,5 +1,8 @@ # mosaic-sync-skills.ps1 -# Syncs canonical skills and links them into agent runtime skill directories. +# Links the INSTALLED canonical skills into agent runtime skill directories. +# Canonical skills ship inside the framework package and are installed into +# ~/.config/mosaic/skills by the framework installer — there is no second +# repository to clone. This script only maintains the runtime links. # Uses directory junctions (no elevation required) with fallback to copies. # PowerShell equivalent of mosaic-sync-skills (bash). $ErrorActionPreference = "Stop" @@ -11,8 +14,6 @@ param( ) $MosaicHome = if ($env:MOSAIC_HOME) { $env:MOSAIC_HOME } else { Join-Path $env:USERPROFILE ".config\mosaic" } -$SkillsRepoUrl = if ($env:MOSAIC_SKILLS_REPO_URL) { $env:MOSAIC_SKILLS_REPO_URL } else { "https://git.mosaicstack.dev/mosaic/agent-skills.git" } -$SkillsRepoDir = if ($env:MOSAIC_SKILLS_REPO_DIR) { $env:MOSAIC_SKILLS_REPO_DIR } else { Join-Path $MosaicHome "sources\agent-skills" } $MosaicSkillsDir = Join-Path $MosaicHome "skills" $MosaicLocalSkillsDir = Join-Path $MosaicHome "skills-local" @@ -20,45 +21,25 @@ if ($Help) { Write-Host @" Usage: mosaic-sync-skills.ps1 [-LinkOnly] [-NoLink] [-Help] -Sync canonical skills into ~/.config/mosaic/skills and link all Mosaic skills -into runtime skill directories using directory junctions. +Link installed skills from ~/.config/mosaic/{skills,skills-local} into runtime +skill directories using directory junctions. Canonical skills arrive with the +framework installer; this script never clones or pulls a second repository. Options: - -LinkOnly Skip git clone/pull and only relink - -NoLink Sync canonical skills but do not update runtime links + -LinkOnly Accepted for compatibility; linking is now the whole job + -NoLink Do nothing (kept for compatibility) -Help Show help "@ exit 0 } -foreach ($d in @($MosaicHome, $MosaicSkillsDir, $MosaicLocalSkillsDir)) { - if (-not (Test-Path $d)) { New-Item -ItemType Directory -Path $d -Force | Out-Null } +if ($NoLink) { + Write-Host "[mosaic-skills] Nothing to do (-NoLink; canonical skills ship with the installer)" + exit 0 } -# Fetch skills from git -if (-not $LinkOnly) { - if (Test-Path (Join-Path $SkillsRepoDir ".git")) { - Write-Host "[mosaic-skills] Updating skills source: $SkillsRepoDir" - git -C $SkillsRepoDir pull --rebase - } - else { - Write-Host "[mosaic-skills] Cloning skills source to: $SkillsRepoDir" - $parentDir = Split-Path $SkillsRepoDir -Parent - if (-not (Test-Path $parentDir)) { New-Item -ItemType Directory -Path $parentDir -Force | Out-Null } - git clone $SkillsRepoUrl $SkillsRepoDir - } - - $sourceSkillsDir = Join-Path $SkillsRepoDir "skills" - if (-not (Test-Path $sourceSkillsDir)) { - Write-Host "[mosaic-skills] Missing source skills dir: $sourceSkillsDir" -ForegroundColor Red - exit 1 - } - - # Sync: remove old, copy new - if (Test-Path $MosaicSkillsDir) { - Get-ChildItem $MosaicSkillsDir | Remove-Item -Recurse -Force - } - Copy-Item "$sourceSkillsDir\*" $MosaicSkillsDir -Recurse -Force +foreach ($d in @($MosaicHome, $MosaicSkillsDir, $MosaicLocalSkillsDir)) { + if (-not (Test-Path $d)) { New-Item -ItemType Directory -Path $d -Force | Out-Null } } if (-not (Test-Path $MosaicSkillsDir)) { diff --git a/packages/mosaic/src/commands/launch.ts b/packages/mosaic/src/commands/launch.ts index 5593c3fd..5a0b7053 100644 --- a/packages/mosaic/src/commands/launch.ts +++ b/packages/mosaic/src/commands/launch.ts @@ -1450,7 +1450,7 @@ export function registerLaunchCommands(program: Command): void { // Direct framework script delegates const directCommands: Record = { init: { desc: 'Generate SOUL.md (agent identity contract)', script: 'mosaic-init' }, - sync: { desc: 'Sync skills from canonical source', script: 'mosaic-sync-skills' }, + sync: { desc: 'Link installed skills into runtime homes', script: 'mosaic-sync-skills' }, bootstrap: { desc: 'Bootstrap a repo with Mosaic standards', script: 'mosaic-bootstrap-repo', diff --git a/packages/mosaic/src/platform/file-ops.ts b/packages/mosaic/src/platform/file-ops.ts index 55a689e7..a6b8fd10 100644 --- a/packages/mosaic/src/platform/file-ops.ts +++ b/packages/mosaic/src/platform/file-ops.ts @@ -94,7 +94,8 @@ export function syncDirectory( const relPath = relative(relBase, src); const dirName = relPath.split('/').pop() ?? ''; - // Skip any .git directory (top-level or nested, e.g. sources/agent-skills/.git) + // Skip any .git directory (top-level or nested; defensive — the + // framework tree itself is not a git checkout at install time) if (options.excludeGit && (dirName === '.git' || relPath.includes('/.git'))) return; // Skip preserved paths at top level diff --git a/packages/mosaic/src/skills/catalog.ts b/packages/mosaic/src/skills/catalog.ts index 46e754aa..3a65c7e4 100644 --- a/packages/mosaic/src/skills/catalog.ts +++ b/packages/mosaic/src/skills/catalog.ts @@ -20,12 +20,6 @@ export function loadSkillsCatalog(mosaicHome: string): SkillEntry[] { skills.push(...loadSkillsFromDir(canonicalDir, 'canonical')); } - // Fallback to source repo - const sourceDir = join(mosaicHome, 'sources', 'agent-skills', 'skills'); - if (skills.length === 0 && existsSync(sourceDir)) { - skills.push(...loadSkillsFromDir(sourceDir, 'canonical')); - } - // Load local skills const localDir = join(mosaicHome, 'skills-local'); if (existsSync(localDir)) {