From 15830e2f2ab7f5db35f3ef5c9c4efd288bf8a38d Mon Sep 17 00:00:00 2001 From: Jarvis Date: Thu, 2 Apr 2026 19:23:44 -0500 Subject: [PATCH] =?UTF-8?q?feat!:=20unify=20mosaic=20CLI=20=E2=80=94=20nat?= =?UTF-8?q?ive=20launcher,=20no=20bin/=20directory?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit BREAKING CHANGE: ~/.config/mosaic/bin/ is removed entirely. The mosaic npm CLI is now the only executable. ## What changed - **bin/ → deleted**: All scripts moved to tools/_scripts/ (internal) - **mosaic-launch → deleted**: Launcher logic is native TypeScript in packages/cli/src/commands/launch.ts - **mosaic.ps1 → deleted**: PowerShell launcher removed - **Framework install.sh**: Complete rewrite with migration system - **Version tracking**: .framework-version file (schema v2) - **Migration v1→v2**: Auto-removes bin/, cleans old PATH entries from shell profiles ## Native TypeScript launcher (commands/launch.ts) All runtime launch logic ported from bash: - Runtime prompt builder (AGENTS.md + RUNTIME.md + USER.md + TOOLS.md) - Mission context injection (reads .mosaic/orchestrator/mission.json) - PRD status injection (scans docs/PRD.md) - Pre-flight checks (MOSAIC_HOME, AGENTS.md, SOUL.md, runtime binary) - Session lock management with signal cleanup - Per-runtime launch: Claude, Codex, OpenCode, Pi - Yolo mode flags per runtime - Pi skill discovery + extension loading - Framework management (init, doctor, sync, bootstrap) delegates to tools/_scripts/ bash implementations ## Installer - tools/install.sh: detects framework by .framework-version or AGENTS.md - Framework install.sh: migration system with schema versioning - Forward-compatible: add migrations as numbered blocks - No PATH manipulation for framework (npm bin is the only PATH entry) --- packages/cli/src/cli.ts | 5 + packages/cli/src/commands/launch.ts | 531 +++++++++++ packages/mosaic/framework/bin/mosaic | 849 ------------------ packages/mosaic/framework/bin/mosaic.ps1 | 437 --------- packages/mosaic/framework/install.sh | 256 +++--- .../repo/scripts/agent/orchestrator-daemon.sh | 4 +- .../_scripts}/mosaic-bootstrap-repo | 4 +- .../_scripts}/mosaic-clean-runtime | 0 .../{bin => tools/_scripts}/mosaic-critical | 0 .../{bin => tools/_scripts}/mosaic-doctor | 32 +- .../{bin => tools/_scripts}/mosaic-doctor.ps1 | 0 .../_scripts}/mosaic-ensure-excalidraw | 0 .../mosaic-ensure-sequential-thinking | 0 .../mosaic-ensure-sequential-thinking.ps1 | 0 .../{bin => tools/_scripts}/mosaic-init | 8 +- .../{bin => tools/_scripts}/mosaic-init.ps1 | 0 .../_scripts}/mosaic-link-runtime-assets | 4 +- .../_scripts}/mosaic-link-runtime-assets.ps1 | 0 .../_scripts}/mosaic-log-limitation | 0 .../_scripts}/mosaic-migrate-local-skills | 0 .../_scripts}/mosaic-migrate-local-skills.ps1 | 0 .../_scripts}/mosaic-orchestrator-drain | 4 +- .../mosaic-orchestrator-matrix-consume | 0 .../mosaic-orchestrator-matrix-cycle | 6 +- .../mosaic-orchestrator-matrix-publish | 0 .../_scripts}/mosaic-orchestrator-run | 0 .../_scripts}/mosaic-orchestrator-sync-tasks | 0 .../{bin => tools/_scripts}/mosaic-projects | 4 +- .../_scripts}/mosaic-prune-legacy-runtime | 0 .../_scripts}/mosaic-quality-apply | 0 .../_scripts}/mosaic-quality-verify | 0 .../_scripts}/mosaic-release-upgrade | 0 .../_scripts}/mosaic-release-upgrade.ps1 | 0 .../_scripts}/mosaic-session-end | 0 .../_scripts}/mosaic-session-start | 0 .../_scripts}/mosaic-sync-skills | 0 .../_scripts}/mosaic-sync-skills.ps1 | 0 .../{bin => tools/_scripts}/mosaic-upgrade | 0 .../_scripts}/mosaic-upgrade-slaves | 2 +- .../{bin => tools/_scripts}/mosaic-wizard | 0 .../tools/orchestrator/session-run.sh | 6 +- scripts/agent/orchestrator-daemon.sh | 4 +- tools/install.sh | 43 +- 43 files changed, 724 insertions(+), 1475 deletions(-) create mode 100644 packages/cli/src/commands/launch.ts delete mode 100755 packages/mosaic/framework/bin/mosaic delete mode 100644 packages/mosaic/framework/bin/mosaic.ps1 rename packages/mosaic/framework/{bin => tools/_scripts}/mosaic-bootstrap-repo (95%) rename packages/mosaic/framework/{bin => tools/_scripts}/mosaic-clean-runtime (100%) rename packages/mosaic/framework/{bin => tools/_scripts}/mosaic-critical (100%) rename packages/mosaic/framework/{bin => tools/_scripts}/mosaic-doctor (91%) rename packages/mosaic/framework/{bin => tools/_scripts}/mosaic-doctor.ps1 (100%) rename packages/mosaic/framework/{bin => tools/_scripts}/mosaic-ensure-excalidraw (100%) rename packages/mosaic/framework/{bin => tools/_scripts}/mosaic-ensure-sequential-thinking (100%) rename packages/mosaic/framework/{bin => tools/_scripts}/mosaic-ensure-sequential-thinking.ps1 (100%) rename packages/mosaic/framework/{bin => tools/_scripts}/mosaic-init (98%) rename packages/mosaic/framework/{bin => tools/_scripts}/mosaic-init.ps1 (100%) rename packages/mosaic/framework/{bin => tools/_scripts}/mosaic-link-runtime-assets (96%) rename packages/mosaic/framework/{bin => tools/_scripts}/mosaic-link-runtime-assets.ps1 (100%) rename packages/mosaic/framework/{bin => tools/_scripts}/mosaic-log-limitation (100%) rename packages/mosaic/framework/{bin => tools/_scripts}/mosaic-migrate-local-skills (100%) rename packages/mosaic/framework/{bin => tools/_scripts}/mosaic-migrate-local-skills.ps1 (100%) rename packages/mosaic/framework/{bin => tools/_scripts}/mosaic-orchestrator-drain (78%) rename packages/mosaic/framework/{bin => tools/_scripts}/mosaic-orchestrator-matrix-consume (100%) rename packages/mosaic/framework/{bin => tools/_scripts}/mosaic-orchestrator-matrix-cycle (58%) rename packages/mosaic/framework/{bin => tools/_scripts}/mosaic-orchestrator-matrix-publish (100%) rename packages/mosaic/framework/{bin => tools/_scripts}/mosaic-orchestrator-run (100%) rename packages/mosaic/framework/{bin => tools/_scripts}/mosaic-orchestrator-sync-tasks (100%) rename packages/mosaic/framework/{bin => tools/_scripts}/mosaic-projects (97%) rename packages/mosaic/framework/{bin => tools/_scripts}/mosaic-prune-legacy-runtime (100%) rename packages/mosaic/framework/{bin => tools/_scripts}/mosaic-quality-apply (100%) rename packages/mosaic/framework/{bin => tools/_scripts}/mosaic-quality-verify (100%) rename packages/mosaic/framework/{bin => tools/_scripts}/mosaic-release-upgrade (100%) rename packages/mosaic/framework/{bin => tools/_scripts}/mosaic-release-upgrade.ps1 (100%) rename packages/mosaic/framework/{bin => tools/_scripts}/mosaic-session-end (100%) rename packages/mosaic/framework/{bin => tools/_scripts}/mosaic-session-start (100%) rename packages/mosaic/framework/{bin => tools/_scripts}/mosaic-sync-skills (100%) rename packages/mosaic/framework/{bin => tools/_scripts}/mosaic-sync-skills.ps1 (100%) rename packages/mosaic/framework/{bin => tools/_scripts}/mosaic-upgrade (100%) rename packages/mosaic/framework/{bin => tools/_scripts}/mosaic-upgrade-slaves (97%) rename packages/mosaic/framework/{bin => tools/_scripts}/mosaic-wizard (100%) diff --git a/packages/cli/src/cli.ts b/packages/cli/src/cli.ts index dafacdc..e006bf5 100644 --- a/packages/cli/src/cli.ts +++ b/packages/cli/src/cli.ts @@ -6,6 +6,7 @@ import { createQualityRailsCli } from '@mosaic/quality-rails'; import { registerAgentCommand } from './commands/agent.js'; import { registerMissionCommand } from './commands/mission.js'; import { registerPrdyCommand } from './commands/prdy.js'; +import { registerLaunchCommands } from './commands/launch.js'; const _require = createRequire(import.meta.url); const CLI_VERSION: string = (_require('../package.json') as { version: string }).version; @@ -22,6 +23,10 @@ const program = new Command(); program.name('mosaic').description('Mosaic Stack CLI').version(CLI_VERSION); +// ─── runtime launchers + framework commands ──────────────────────────── + +registerLaunchCommands(program); + // ─── login ────────────────────────────────────────────────────────────── program diff --git a/packages/cli/src/commands/launch.ts b/packages/cli/src/commands/launch.ts new file mode 100644 index 0000000..e89a8ce --- /dev/null +++ b/packages/cli/src/commands/launch.ts @@ -0,0 +1,531 @@ +/** + * Native runtime launcher — replaces the bash mosaic-launch script. + * + * Builds a composed runtime prompt from AGENTS.md + RUNTIME.md + USER.md + + * TOOLS.md + mission context + PRD status, then exec's into the target CLI. + */ + +import { execFileSync, execSync, spawnSync } from 'node:child_process'; +import { existsSync, mkdirSync, readFileSync, writeFileSync, readdirSync, rmSync } from 'node:fs'; +import { homedir } from 'node:os'; +import { join, dirname } from 'node:path'; +import type { Command } from 'commander'; + +const MOSAIC_HOME = process.env['MOSAIC_HOME'] ?? join(homedir(), '.config', 'mosaic'); + +type RuntimeName = 'claude' | 'codex' | 'opencode' | 'pi'; + +const RUNTIME_LABELS: Record = { + claude: 'Claude Code', + codex: 'Codex', + opencode: 'OpenCode', + pi: 'Pi', +}; + +// ─── Pre-flight checks ────────────────────────────────────────────────────── + +function checkMosaicHome(): void { + if (!existsSync(MOSAIC_HOME)) { + console.error(`[mosaic] ERROR: ${MOSAIC_HOME} not found.`); + console.error( + '[mosaic] Install: bash <(curl -fsSL https://git.mosaicstack.dev/mosaic/mosaic-stack/raw/branch/main/tools/install.sh)', + ); + process.exit(1); + } +} + +function checkFile(path: string, label: string): void { + if (!existsSync(path)) { + console.error(`[mosaic] ERROR: ${label} not found: ${path}`); + process.exit(1); + } +} + +function checkRuntime(cmd: string): void { + try { + execSync(`which ${cmd}`, { stdio: 'ignore' }); + } catch { + console.error(`[mosaic] ERROR: '${cmd}' not found in PATH.`); + console.error(`[mosaic] Install ${cmd} before launching.`); + process.exit(1); + } +} + +function checkSoul(): void { + const soulPath = join(MOSAIC_HOME, 'SOUL.md'); + if (!existsSync(soulPath)) { + console.log('[mosaic] SOUL.md not found. Running mosaic init...'); + const initBin = join(MOSAIC_HOME, 'tools', '_scripts', 'mosaic-init'); + if (existsSync(initBin)) { + spawnSync(initBin, [], { stdio: 'inherit' }); + } else { + console.error('[mosaic] mosaic-init not found. Run: mosaic wizard'); + process.exit(1); + } + } +} + +function checkSequentialThinking(runtime: string): void { + const checker = join(MOSAIC_HOME, 'tools', '_scripts', 'mosaic-ensure-sequential-thinking'); + if (!existsSync(checker)) return; // Skip if checker doesn't exist + const result = spawnSync(checker, ['--check', '--runtime', runtime], { stdio: 'ignore' }); + if (result.status !== 0) { + console.error('[mosaic] ERROR: sequential-thinking MCP is required but not configured.'); + console.error(`[mosaic] Fix: ${checker} --runtime ${runtime}`); + process.exit(1); + } +} + +// ─── File helpers ──────────────────────────────────────────────────────────── + +function readOptional(path: string): string { + try { + return readFileSync(path, 'utf-8'); + } catch { + return ''; + } +} + +function readJson(path: string): Record | null { + try { + return JSON.parse(readFileSync(path, 'utf-8')) as Record; + } catch { + return null; + } +} + +// ─── Mission context ───────────────────────────────────────────────────────── + +interface MissionInfo { + name: string; + id: string; + status: string; + milestoneCount: number; + completedCount: number; +} + +function detectMission(): MissionInfo | null { + const missionFile = '.mosaic/orchestrator/mission.json'; + const data = readJson(missionFile); + if (!data) return null; + + const status = String(data['status'] ?? 'inactive'); + if (status !== 'active' && status !== 'paused') return null; + + const milestones = Array.isArray(data['milestones']) ? data['milestones'] : []; + const completed = milestones.filter( + (m) => + typeof m === 'object' && + m !== null && + (m as Record)['status'] === 'completed', + ); + + return { + name: String(data['name'] ?? 'unnamed'), + id: String(data['mission_id'] ?? ''), + status, + milestoneCount: milestones.length, + completedCount: completed.length, + }; +} + +function buildMissionBlock(mission: MissionInfo): string { + return `# ACTIVE MISSION — HARD GATE (Read Before Anything Else) + +An active orchestration mission exists in this project. This is a BLOCKING requirement. + +**Mission:** ${mission.name} +**ID:** ${mission.id} +**Status:** ${mission.status} +**Milestones:** ${mission.completedCount} / ${mission.milestoneCount} completed + +## MANDATORY — Before ANY Response to the User + +You MUST complete these steps before responding to any user message, including simple greetings: + +1. Read \`~/.config/mosaic/guides/ORCHESTRATOR-PROTOCOL.md\` (mission lifecycle protocol) +2. Read \`docs/MISSION-MANIFEST.md\` for full mission scope, milestones, and success criteria +3. Read the latest scratchpad in \`docs/scratchpads/\` for session history, decisions, and corrections +4. Read \`docs/TASKS.md\` for current task state (what is done, what is next) +5. After reading all four, acknowledge the mission state to the user before proceeding + +If the user gives a task, execute it within the mission context. If no task is given, present mission status and ask how to proceed. + +`; +} + +// ─── PRD status ────────────────────────────────────────────────────────────── + +function buildPrdBlock(): string { + const prdFile = 'docs/PRD.md'; + if (!existsSync(prdFile)) return ''; + + const content = readFileSync(prdFile, 'utf-8'); + const patterns = [ + /^#{2,3} .*(problem statement|objective)/im, + /^#{2,3} .*(scope|non.goal|out of scope|in.scope)/im, + /^#{2,3} .*(user stor|stakeholder|user.*requirement)/im, + /^#{2,3} .*functional requirement/im, + /^#{2,3} .*non.functional/im, + /^#{2,3} .*acceptance criteria/im, + /^#{2,3} .*(technical consideration|constraint|dependenc)/im, + /^#{2,3} .*(risk|open question)/im, + /^#{2,3} .*(success metric|test|verification)/im, + /^#{2,3} .*(milestone|delivery|scope version)/im, + ]; + + let sections = 0; + for (const pattern of patterns) { + if (pattern.test(content)) sections++; + } + + const assumptions = (content.match(/ASSUMPTION:/g) ?? []).length; + const status = sections < 10 ? `incomplete (${sections}/10 sections)` : 'ready'; + + return ` +# PRD Status + +- **File:** docs/PRD.md +- **Status:** ${status} +- **Assumptions:** ${assumptions} + +`; +} + +// ─── Runtime prompt builder ────────────────────────────────────────────────── + +function buildRuntimePrompt(runtime: RuntimeName): string { + const runtimeContractPaths: Record = { + claude: join(MOSAIC_HOME, 'runtime', 'claude', 'RUNTIME.md'), + codex: join(MOSAIC_HOME, 'runtime', 'codex', 'RUNTIME.md'), + opencode: join(MOSAIC_HOME, 'runtime', 'opencode', 'RUNTIME.md'), + pi: join(MOSAIC_HOME, 'runtime', 'pi', 'RUNTIME.md'), + }; + + const runtimeFile = runtimeContractPaths[runtime]; + checkFile(runtimeFile, `Runtime contract for ${runtime}`); + + const parts: string[] = []; + + // Mission context (injected first) + const mission = detectMission(); + if (mission) { + parts.push(buildMissionBlock(mission)); + } + + // PRD status + const prdBlock = buildPrdBlock(); + if (prdBlock) parts.push(prdBlock); + + // Hard gate + parts.push(`# Mosaic Launcher Runtime Contract (Hard Gate) + +This contract is injected by \`mosaic\` launch and is mandatory. + +First assistant response MUST start with exactly one mode declaration line: +1. Orchestration mission: \`Now initiating Orchestrator mode...\` +2. Implementation mission: \`Now initiating Delivery mode...\` +3. Review-only mission: \`Now initiating Review mode...\` + +No tool call or implementation step may occur before that first line. + +Mosaic hard gates OVERRIDE runtime-default caution for routine delivery operations. +For required push/merge/issue-close/release actions, execute without routine confirmation prompts. +`); + + // AGENTS.md + parts.push(readFileSync(join(MOSAIC_HOME, 'AGENTS.md'), 'utf-8')); + + // USER.md + const user = readOptional(join(MOSAIC_HOME, 'USER.md')); + if (user) parts.push('\n\n# User Profile\n\n' + user); + + // TOOLS.md + const tools = readOptional(join(MOSAIC_HOME, 'TOOLS.md')); + if (tools) parts.push('\n\n# Machine Tools\n\n' + tools); + + // Runtime-specific contract + parts.push('\n\n# Runtime-Specific Contract\n\n' + readFileSync(runtimeFile, 'utf-8')); + + return parts.join('\n'); +} + +// ─── Session lock ──────────────────────────────────────────────────────────── + +function writeSessionLock(runtime: string): void { + const missionFile = '.mosaic/orchestrator/mission.json'; + const lockFile = '.mosaic/orchestrator/session.lock'; + const data = readJson(missionFile); + if (!data) return; + + const status = String(data['status'] ?? 'inactive'); + if (status !== 'active' && status !== 'paused') return; + + const sessionId = `${runtime}-${new Date().toISOString().replace(/[:.]/g, '-')}-${process.pid}`; + const lock = { + session_id: sessionId, + runtime, + pid: process.pid, + started_at: new Date().toISOString(), + project_path: process.cwd(), + milestone_id: '', + }; + + try { + mkdirSync(dirname(lockFile), { recursive: true }); + writeFileSync(lockFile, JSON.stringify(lock, null, 2) + '\n'); + + // Clean up on exit + const cleanup = () => { + try { + rmSync(lockFile, { force: true }); + } catch { + // best-effort + } + }; + process.on('exit', cleanup); + process.on('SIGINT', () => { + cleanup(); + process.exit(130); + }); + process.on('SIGTERM', () => { + cleanup(); + process.exit(143); + }); + } catch { + // Non-fatal + } +} + +// ─── Resumable session advisory ────────────────────────────────────────────── + +function checkResumableSession(): void { + const lockFile = '.mosaic/orchestrator/session.lock'; + const missionFile = '.mosaic/orchestrator/mission.json'; + + if (existsSync(lockFile)) { + const lock = readJson(lockFile); + if (lock) { + const pid = Number(lock['pid'] ?? 0); + if (pid > 0) { + try { + process.kill(pid, 0); // Check if alive + } catch { + // Process is dead — stale lock + rmSync(lockFile, { force: true }); + console.log(`[mosaic] Cleaned up stale session lock (PID ${pid} no longer running).\n`); + } + } + } + } else if (existsSync(missionFile)) { + const data = readJson(missionFile); + if (data && data['status'] === 'active') { + console.log('[mosaic] Active mission detected. Generate continuation prompt with:'); + console.log('[mosaic] mosaic coord continue\n'); + } + } +} + +// ─── Write config for runtimes that read from fixed paths ──────────────────── + +function ensureRuntimeConfig(runtime: RuntimeName, destPath: string): void { + const prompt = buildRuntimePrompt(runtime); + mkdirSync(dirname(destPath), { recursive: true }); + const existing = readOptional(destPath); + if (existing !== prompt) { + writeFileSync(destPath, prompt); + } +} + +// ─── Pi skill/extension discovery ──────────────────────────────────────────── + +function discoverPiSkills(): string[] { + const args: string[] = []; + for (const skillsRoot of [join(MOSAIC_HOME, 'skills'), join(MOSAIC_HOME, 'skills-local')]) { + if (!existsSync(skillsRoot)) continue; + try { + for (const entry of readdirSync(skillsRoot, { withFileTypes: true })) { + if (!entry.isDirectory()) continue; + const skillDir = join(skillsRoot, entry.name); + if (existsSync(join(skillDir, 'SKILL.md'))) { + args.push('--skill', skillDir); + } + } + } catch { + // skip + } + } + return args; +} + +function discoverPiExtension(): string[] { + const ext = join(MOSAIC_HOME, 'runtime', 'pi', 'mosaic-extension.ts'); + return existsSync(ext) ? ['--extension', ext] : []; +} + +// ─── Launch functions ──────────────────────────────────────────────────────── + +function getMissionPrompt(): string { + const mission = detectMission(); + if (!mission) return ''; + return `Active mission detected: ${mission.name}. Read the mission state files and report status.`; +} + +function launchRuntime(runtime: RuntimeName, args: string[], yolo: boolean): never { + checkMosaicHome(); + checkFile(join(MOSAIC_HOME, 'AGENTS.md'), 'AGENTS.md'); + checkSoul(); + checkRuntime(runtime); + + // Pi doesn't need sequential-thinking (has native thinking levels) + if (runtime !== 'pi') { + checkSequentialThinking(runtime); + } + + checkResumableSession(); + + const missionPrompt = getMissionPrompt(); + const hasMissionNoArgs = missionPrompt && args.length === 0; + const label = RUNTIME_LABELS[runtime]; + const modeStr = yolo ? ' in YOLO mode' : ''; + const missionStr = hasMissionNoArgs ? ' (active mission detected)' : ''; + + writeSessionLock(runtime); + + switch (runtime) { + case 'claude': { + const prompt = buildRuntimePrompt('claude'); + const cliArgs = yolo ? ['--dangerously-skip-permissions'] : []; + cliArgs.push('--append-system-prompt', prompt); + if (hasMissionNoArgs) { + cliArgs.push(missionPrompt); + } else { + cliArgs.push(...args); + } + console.log(`[mosaic] Launching ${label}${modeStr}${missionStr}...`); + execRuntime('claude', cliArgs); + break; + } + + case 'codex': { + ensureRuntimeConfig('codex', join(homedir(), '.codex', 'instructions.md')); + const cliArgs = yolo ? ['--dangerously-bypass-approvals-and-sandbox'] : []; + if (hasMissionNoArgs) { + cliArgs.push(missionPrompt); + } else { + cliArgs.push(...args); + } + console.log(`[mosaic] Launching ${label}${modeStr}${missionStr}...`); + execRuntime('codex', cliArgs); + break; + } + + case 'opencode': { + ensureRuntimeConfig('opencode', join(homedir(), '.config', 'opencode', 'AGENTS.md')); + console.log(`[mosaic] Launching ${label}${modeStr}...`); + execRuntime('opencode', args); + break; + } + + case 'pi': { + const prompt = buildRuntimePrompt('pi'); + const cliArgs = ['--append-system-prompt', prompt]; + cliArgs.push(...discoverPiSkills()); + cliArgs.push(...discoverPiExtension()); + if (hasMissionNoArgs) { + cliArgs.push(missionPrompt); + } else { + cliArgs.push(...args); + } + console.log(`[mosaic] Launching ${label}${modeStr}${missionStr}...`); + execRuntime('pi', cliArgs); + break; + } + } + + process.exit(0); // Unreachable but satisfies never +} + +/** exec into the runtime, replacing the current process. */ +function execRuntime(cmd: string, args: string[]): void { + try { + // Use execFileSync with inherited stdio to replace the process + const result = spawnSync(cmd, args, { + stdio: 'inherit', + env: process.env, + }); + process.exit(result.status ?? 0); + } catch (err) { + console.error(`[mosaic] Failed to launch ${cmd}:`, err instanceof Error ? err.message : err); + process.exit(1); + } +} + +// ─── Framework script delegation (for tools that remain in bash) ───────────── + +function delegateToFrameworkScript(script: string, args: string[]): never { + const scriptPath = join(MOSAIC_HOME, 'tools', '_scripts', script); + if (!existsSync(scriptPath)) { + console.error(`[mosaic] Script not found: ${scriptPath}`); + process.exit(1); + } + try { + execFileSync(scriptPath, args, { stdio: 'inherit' }); + process.exit(0); + } catch (err) { + process.exit((err as { status?: number }).status ?? 1); + } +} + +// ─── Commander registration ────────────────────────────────────────────────── + +export function registerLaunchCommands(program: Command): void { + // Runtime launchers + for (const runtime of ['claude', 'codex', 'opencode', 'pi'] as const) { + program + .command(runtime) + .description(`Launch ${RUNTIME_LABELS[runtime]} with Mosaic injection`) + .allowUnknownOption(true) + .allowExcessArguments(true) + .action((_opts: unknown, cmd: Command) => { + launchRuntime(runtime, cmd.args, false); + }); + } + + // Yolo mode + program + .command('yolo ') + .description('Launch a runtime in dangerous-permissions mode (claude|codex|opencode|pi)') + .allowUnknownOption(true) + .allowExcessArguments(true) + .action((runtime: string, _opts: unknown, cmd: Command) => { + const valid: RuntimeName[] = ['claude', 'codex', 'opencode', 'pi']; + if (!valid.includes(runtime as RuntimeName)) { + console.error( + `[mosaic] ERROR: Unsupported yolo runtime '${runtime}'. Use: ${valid.join('|')}`, + ); + process.exit(1); + } + launchRuntime(runtime as RuntimeName, cmd.args, true); + }); + + // Framework management commands (delegate to bash scripts) + const frameworkCommands: Record = { + init: { desc: 'Generate SOUL.md (agent identity contract)', script: 'mosaic-init' }, + doctor: { desc: 'Health audit — detect drift and missing files', script: 'mosaic-doctor' }, + sync: { desc: 'Sync skills from canonical source', script: 'mosaic-sync-skills' }, + bootstrap: { desc: 'Bootstrap a repo with Mosaic standards', script: 'mosaic-bootstrap-repo' }, + }; + + for (const [name, { desc, script }] of Object.entries(frameworkCommands)) { + program + .command(name) + .description(desc) + .allowUnknownOption(true) + .allowExcessArguments(true) + .action((_opts: unknown, cmd: Command) => { + checkMosaicHome(); + delegateToFrameworkScript(script, cmd.args); + }); + } +} diff --git a/packages/mosaic/framework/bin/mosaic b/packages/mosaic/framework/bin/mosaic deleted file mode 100755 index 5a686f9..0000000 --- a/packages/mosaic/framework/bin/mosaic +++ /dev/null @@ -1,849 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -# mosaic — Unified agent launcher and management CLI -# -# AGENTS.md is the global policy source for all agent sessions. -# The launcher injects a composed runtime contract (AGENTS + runtime reference). -# -# Usage: -# mosaic claude [args...] Launch Claude Code with runtime contract injected -# mosaic opencode [args...] Launch OpenCode with runtime contract injected -# mosaic codex [args...] Launch Codex with runtime contract injected -# mosaic yolo [args...] Launch runtime in dangerous-permissions mode -# mosaic --yolo [args...] Alias for yolo -# mosaic init [args...] Generate SOUL.md interactively -# mosaic doctor [args...] Health audit -# mosaic sync [args...] Sync skills -# mosaic seq [subcommand] sequential-thinking MCP management (check/fix/start) -# mosaic bootstrap Bootstrap a repo -# mosaic upgrade release Upgrade installed Mosaic release -# mosaic upgrade check Check release upgrade status (no changes) -# mosaic upgrade project [args] Upgrade project-local stale files - -MOSAIC_HOME="${MOSAIC_HOME:-$HOME/.config/mosaic}" -VERSION="0.1.0" - -usage() { - cat < [args...] - -Agent Launchers: - pi [args...] Launch Pi with runtime contract injected (recommended) - claude [args...] Launch Claude Code with runtime contract injected - opencode [args...] Launch OpenCode with runtime contract injected - codex [args...] Launch Codex with runtime contract injected - yolo [args...] Dangerous mode for claude|codex|opencode|pi - --yolo [args...] Alias for yolo - -Management: - init [args...] Generate SOUL.md (agent identity contract) - doctor [args...] Audit runtime state and detect drift - sync [args...] Sync skills from canonical source - seq [subcommand] sequential-thinking MCP management: - check [--runtime ] [--strict] - fix [--runtime ] - start - bootstrap Bootstrap a repo with Mosaic standards - upgrade [mode] [args] Upgrade release (default) or project files - upgrade check Check release upgrade status (no changes) - release-upgrade [...] Upgrade installed Mosaic release - project-upgrade [...] Clean up stale SOUL.md/CLAUDE.md in a project - -PRD: - prdy PRD creation and validation - init Create docs/PRD.md via guided runtime session - update Update existing PRD via guided runtime session - validate Check PRD completeness (bash-only) - status Quick PRD health check (one-liner) - -Coordinator (r0): - coord Manual coordinator tools - init Initialize a new mission - mission Show mission progress dashboard - status Check agent session health - continue Generate continuation prompt - run Generate context and launch selected runtime - resume Crash recovery - -Options: - -h, --help Show this help - -v, --version Show version - -All arguments after the command are forwarded to the target CLI. -USAGE -} - -# Pre-flight checks -check_mosaic_home() { - if [[ ! -d "$MOSAIC_HOME" ]]; then - echo "[mosaic] ERROR: ~/.config/mosaic not found." >&2 - echo "[mosaic] Install with: bash <(curl -fsSL https://git.mosaicstack.dev/mosaic/mosaic-stack/raw/branch/main/tools/install.sh)" >&2 - exit 1 - fi -} - -check_agents_md() { - if [[ ! -f "$MOSAIC_HOME/AGENTS.md" ]]; then - echo "[mosaic] ERROR: ~/.config/mosaic/AGENTS.md not found." >&2 - echo "[mosaic] Re-run the installer: bash <(curl -fsSL https://git.mosaicstack.dev/mosaic/mosaic-stack/raw/branch/main/tools/install.sh)" >&2 - exit 1 - fi -} - -check_soul() { - if [[ ! -f "$MOSAIC_HOME/SOUL.md" ]]; then - echo "[mosaic] SOUL.md not found. Running mosaic init..." - "$MOSAIC_HOME/bin/mosaic-init" - fi -} - -check_runtime() { - local cmd="$1" - if ! command -v "$cmd" >/dev/null 2>&1; then - echo "[mosaic] ERROR: '$cmd' not found in PATH." >&2 - echo "[mosaic] Install $cmd before launching." >&2 - exit 1 - fi -} - -check_sequential_thinking() { - local runtime="${1:-all}" - local checker="$MOSAIC_HOME/bin/mosaic-ensure-sequential-thinking" - if [[ ! -x "$checker" ]]; then - echo "[mosaic] ERROR: sequential-thinking checker missing: $checker" >&2 - exit 1 - fi - if ! "$checker" --check --runtime "$runtime" >/dev/null 2>&1; then - echo "[mosaic] ERROR: sequential-thinking MCP is required but not configured." >&2 - echo "[mosaic] Fix config: $checker --runtime $runtime" >&2 - echo "[mosaic] Or run: mosaic seq fix --runtime $runtime" >&2 - echo "[mosaic] Manual server start: mosaic seq start" >&2 - exit 1 - fi -} - -runtime_contract_path() { - local runtime="$1" - case "$runtime" in - claude) echo "$MOSAIC_HOME/runtime/claude/RUNTIME.md" ;; - codex) echo "$MOSAIC_HOME/runtime/codex/RUNTIME.md" ;; - opencode) echo "$MOSAIC_HOME/runtime/opencode/RUNTIME.md" ;; - pi) echo "$MOSAIC_HOME/runtime/pi/RUNTIME.md" ;; - *) - echo "[mosaic] ERROR: unsupported runtime '$runtime' for runtime contract." >&2 - exit 1 - ;; - esac -} - -build_runtime_prompt() { - local runtime="$1" - local runtime_file - runtime_file="$(runtime_contract_path "$runtime")" - if [[ ! -f "$runtime_file" ]]; then - echo "[mosaic] ERROR: runtime contract not found: $runtime_file" >&2 - exit 1 - fi - - # Inject active mission context FIRST so the agent sees it immediately - local mission_file=".mosaic/orchestrator/mission.json" - if [[ -f "$mission_file" ]] && command -v jq &>/dev/null; then - local m_status - m_status="$(jq -r '.status // "inactive"' "$mission_file" 2>/dev/null)" - if [[ "$m_status" == "active" || "$m_status" == "paused" ]]; then - local m_name m_id m_count m_completed - m_name="$(jq -r '.name // "unnamed"' "$mission_file")" - m_id="$(jq -r '.mission_id // ""' "$mission_file")" - m_count="$(jq '.milestones | length' "$mission_file")" - m_completed="$(jq '[.milestones[] | select(.status == "completed")] | length' "$mission_file")" - - cat </dev/null && prd_sections=$((prd_sections + 1)) - done - prd_assumptions=$(grep -c 'ASSUMPTION:' "$prd_file" 2>/dev/null || echo 0) - - local prd_status="ready" - (( prd_sections < 10 )) && prd_status="incomplete ($prd_sections/10 sections)" - - cat < "$tmp" - if ! cmp -s "$tmp" "$dst" 2>/dev/null; then - mv "$tmp" "$dst" - else - rm -f "$tmp" - fi -} - -# Detect active mission and return an initial prompt if one exists. -# Sets MOSAIC_MISSION_PROMPT as a side effect. -_detect_mission_prompt() { - MOSAIC_MISSION_PROMPT="" - local mission_file=".mosaic/orchestrator/mission.json" - if [[ -f "$mission_file" ]] && command -v jq &>/dev/null; then - local m_status - m_status="$(jq -r '.status // "inactive"' "$mission_file" 2>/dev/null)" - if [[ "$m_status" == "active" || "$m_status" == "paused" ]]; then - local m_name - m_name="$(jq -r '.name // "unnamed"' "$mission_file")" - MOSAIC_MISSION_PROMPT="Active mission detected: ${m_name}. Read the mission state files and report status." - fi - fi -} - -# Write a session lock if an active mission exists in the current directory. -# Called before exec so $$ captures the PID that will become the agent process. -_write_launcher_session_lock() { - local runtime="$1" - local mission_file=".mosaic/orchestrator/mission.json" - local lock_file=".mosaic/orchestrator/session.lock" - - # Only write lock if mission exists and is active - [[ -f "$mission_file" ]] || return 0 - command -v jq &>/dev/null || return 0 - - local m_status - m_status="$(jq -r '.status // "inactive"' "$mission_file" 2>/dev/null)" - [[ "$m_status" == "active" || "$m_status" == "paused" ]] || return 0 - - local session_id - session_id="${runtime}-$(date +%Y%m%d-%H%M%S)-$$" - - jq -n \ - --arg sid "$session_id" \ - --arg rt "$runtime" \ - --arg pid "$$" \ - --arg ts "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \ - --arg pp "$(pwd)" \ - --arg mid "" \ - '{ - session_id: $sid, - runtime: $rt, - pid: ($pid | tonumber), - started_at: $ts, - project_path: $pp, - milestone_id: $mid - }' > "$lock_file" -} - -# Clean up session lock on exit (covers normal exit + signals). -# Registered via trap after _write_launcher_session_lock succeeds. -_cleanup_session_lock() { - rm -f ".mosaic/orchestrator/session.lock" 2>/dev/null -} - -# Launcher functions -launch_claude() { - check_mosaic_home - check_agents_md - check_soul - check_runtime "claude" - check_sequential_thinking "claude" - - _check_resumable_session - - # Claude supports --append-system-prompt for direct injection - local runtime_prompt - runtime_prompt="$(build_runtime_prompt "claude")" - - # If active mission exists and no user prompt was given, inject initial prompt - _detect_mission_prompt - _write_launcher_session_lock "claude" - trap _cleanup_session_lock EXIT INT TERM - if [[ -n "$MOSAIC_MISSION_PROMPT" && $# -eq 0 ]]; then - echo "[mosaic] Launching Claude Code (active mission detected)..." - exec claude --append-system-prompt "$runtime_prompt" "$MOSAIC_MISSION_PROMPT" - else - echo "[mosaic] Launching Claude Code..." - exec claude --append-system-prompt "$runtime_prompt" "$@" - fi -} - -launch_opencode() { - check_mosaic_home - check_agents_md - check_soul - check_runtime "opencode" - check_sequential_thinking "opencode" - - _check_resumable_session - - # OpenCode reads from ~/.config/opencode/AGENTS.md - ensure_runtime_config "opencode" "$HOME/.config/opencode/AGENTS.md" - _write_launcher_session_lock "opencode" - trap _cleanup_session_lock EXIT INT TERM - echo "[mosaic] Launching OpenCode..." - exec opencode "$@" -} - -launch_codex() { - check_mosaic_home - check_agents_md - check_soul - check_runtime "codex" - check_sequential_thinking "codex" - - _check_resumable_session - - # Codex reads from ~/.codex/instructions.md - ensure_runtime_config "codex" "$HOME/.codex/instructions.md" - _detect_mission_prompt - _write_launcher_session_lock "codex" - trap _cleanup_session_lock EXIT INT TERM - if [[ -n "$MOSAIC_MISSION_PROMPT" && $# -eq 0 ]]; then - echo "[mosaic] Launching Codex (active mission detected)..." - exec codex "$MOSAIC_MISSION_PROMPT" - else - echo "[mosaic] Launching Codex..." - exec codex "$@" - fi -} - -launch_pi() { - check_mosaic_home - check_agents_md - check_soul - check_runtime "pi" - # Pi has native thinking levels — no sequential-thinking gate required - - _check_resumable_session - - local runtime_prompt - runtime_prompt="$(build_runtime_prompt "pi")" - - # Build skill args from Mosaic skills directories (canonical + local) - local -a skill_args=() - for skills_root in "$MOSAIC_HOME/skills" "$MOSAIC_HOME/skills-local"; do - [[ -d "$skills_root" ]] || continue - for skill_dir in "$skills_root"/*/; do - [[ -f "${skill_dir}SKILL.md" ]] && skill_args+=(--skill "$skill_dir") - done - done - - # Load Mosaic extension if present - local -a ext_args=() - local mosaic_ext="$MOSAIC_HOME/runtime/pi/mosaic-extension.ts" - [[ -f "$mosaic_ext" ]] && ext_args=(--extension "$mosaic_ext") - - _detect_mission_prompt - _write_launcher_session_lock "pi" - trap _cleanup_session_lock EXIT INT TERM - if [[ -n "$MOSAIC_MISSION_PROMPT" && $# -eq 0 ]]; then - echo "[mosaic] Launching Pi (active mission detected)..." - exec pi --append-system-prompt "$runtime_prompt" \ - "${skill_args[@]}" "${ext_args[@]}" "$MOSAIC_MISSION_PROMPT" - else - echo "[mosaic] Launching Pi..." - exec pi --append-system-prompt "$runtime_prompt" \ - "${skill_args[@]}" "${ext_args[@]}" "$@" - fi -} - -launch_yolo() { - if [[ $# -eq 0 ]]; then - echo "[mosaic] ERROR: yolo requires a runtime (claude|codex|opencode|pi)." >&2 - echo "[mosaic] Example: mosaic yolo claude" >&2 - exit 1 - fi - - local runtime="$1" - shift - - case "$runtime" in - claude) - check_mosaic_home - check_agents_md - check_soul - check_runtime "claude" - check_sequential_thinking "claude" - - # Claude uses an explicit dangerous permissions flag. - local runtime_prompt - runtime_prompt="$(build_runtime_prompt "claude")" - - _detect_mission_prompt - _write_launcher_session_lock "claude" - trap _cleanup_session_lock EXIT INT TERM - if [[ -n "$MOSAIC_MISSION_PROMPT" && $# -eq 0 ]]; then - echo "[mosaic] Launching Claude Code in YOLO mode (active mission detected)..." - exec claude --dangerously-skip-permissions --append-system-prompt "$runtime_prompt" "$MOSAIC_MISSION_PROMPT" - else - echo "[mosaic] Launching Claude Code in YOLO mode (dangerous permissions enabled)..." - exec claude --dangerously-skip-permissions --append-system-prompt "$runtime_prompt" "$@" - fi - ;; - codex) - check_mosaic_home - check_agents_md - check_soul - check_runtime "codex" - check_sequential_thinking "codex" - - # Codex reads instructions.md from ~/.codex and supports a direct dangerous flag. - ensure_runtime_config "codex" "$HOME/.codex/instructions.md" - _detect_mission_prompt - _write_launcher_session_lock "codex" - trap _cleanup_session_lock EXIT INT TERM - if [[ -n "$MOSAIC_MISSION_PROMPT" && $# -eq 0 ]]; then - echo "[mosaic] Launching Codex in YOLO mode (active mission detected)..." - exec codex --dangerously-bypass-approvals-and-sandbox "$MOSAIC_MISSION_PROMPT" - else - echo "[mosaic] Launching Codex in YOLO mode (dangerous permissions enabled)..." - exec codex --dangerously-bypass-approvals-and-sandbox "$@" - fi - ;; - opencode) - check_mosaic_home - check_agents_md - check_soul - check_runtime "opencode" - check_sequential_thinking "opencode" - - # OpenCode defaults to allow-all permissions unless user config restricts them. - ensure_runtime_config "opencode" "$HOME/.config/opencode/AGENTS.md" - _write_launcher_session_lock "opencode" - trap _cleanup_session_lock EXIT INT TERM - echo "[mosaic] Launching OpenCode in YOLO mode..." - exec opencode "$@" - ;; - pi) - # Pi has no permission restrictions — yolo is identical to normal launch - launch_pi "$@" - ;; - *) - echo "[mosaic] ERROR: Unsupported yolo runtime '$runtime'. Use claude|codex|opencode|pi." >&2 - exit 1 - ;; - esac -} - -# Delegate to existing scripts -run_init() { - # Prefer wizard if Node.js and bundle are available - local wizard_bin="$MOSAIC_HOME/dist/mosaic-wizard.mjs" - if command -v node >/dev/null 2>&1 && [[ -f "$wizard_bin" ]]; then - exec node "$wizard_bin" "$@" - fi - # Fallback to legacy bash wizard - check_mosaic_home - exec "$MOSAIC_HOME/bin/mosaic-init" "$@" -} - -run_doctor() { - check_mosaic_home - exec "$MOSAIC_HOME/bin/mosaic-doctor" "$@" -} - -run_sync() { - check_mosaic_home - exec "$MOSAIC_HOME/bin/mosaic-sync-skills" "$@" -} - -run_seq() { - check_mosaic_home - local checker="$MOSAIC_HOME/bin/mosaic-ensure-sequential-thinking" - local action="${1:-check}" - - case "$action" in - check) - shift || true - exec "$checker" --check "$@" - ;; - fix|apply) - shift || true - exec "$checker" "$@" - ;; - start) - shift || true - check_runtime "npx" - echo "[mosaic] Starting sequential-thinking MCP server..." - exec npx -y @modelcontextprotocol/server-sequential-thinking "$@" - ;; - *) - echo "[mosaic] ERROR: Unknown seq subcommand '$action'." >&2 - echo "[mosaic] Use: mosaic seq check|fix|start" >&2 - exit 1 - ;; - esac -} - -run_coord() { - check_mosaic_home - local runtime="claude" - local runtime_flag="" - local yolo_flag="" - local -a coord_args=() - - while [[ $# -gt 0 ]]; do - case "$1" in - --claude|--codex|--pi) - local selected_runtime="${1#--}" - if [[ -n "$runtime_flag" ]] && [[ "$runtime" != "$selected_runtime" ]]; then - echo "[mosaic] ERROR: --claude, --codex, and --pi are mutually exclusive for 'mosaic coord'." >&2 - exit 1 - fi - runtime="$selected_runtime" - runtime_flag="$1" - shift - ;; - --yolo) - yolo_flag="--yolo" - shift - ;; - *) - coord_args+=("$1") - shift - ;; - esac - done - - local subcmd="${coord_args[0]:-help}" - if (( ${#coord_args[@]} > 1 )); then - set -- "${coord_args[@]:1}" - else - set -- - fi - - local tool_dir="$MOSAIC_HOME/tools/orchestrator" - - case "$subcmd" in - status|session) - MOSAIC_COORD_RUNTIME="$runtime" exec bash "$tool_dir/session-status.sh" "$@" - ;; - init) - MOSAIC_COORD_RUNTIME="$runtime" exec bash "$tool_dir/mission-init.sh" "$@" - ;; - mission|progress) - MOSAIC_COORD_RUNTIME="$runtime" exec bash "$tool_dir/mission-status.sh" "$@" - ;; - continue|next) - MOSAIC_COORD_RUNTIME="$runtime" exec bash "$tool_dir/continue-prompt.sh" "$@" - ;; - run|start) - MOSAIC_COORD_RUNTIME="$runtime" exec bash "$tool_dir/session-run.sh" ${yolo_flag:+"$yolo_flag"} "$@" - ;; - smoke|test) - MOSAIC_COORD_RUNTIME="$runtime" exec bash "$tool_dir/smoke-test.sh" "$@" - ;; - resume|recover) - MOSAIC_COORD_RUNTIME="$runtime" exec bash "$tool_dir/session-resume.sh" "$@" - ;; - help|*) - cat < [opts] Initialize a new mission - mission [--project ] Show mission progress dashboard - status [--project ] Check agent session health - continue [--project ] Generate continuation prompt for next session - run [--project ] Generate context and launch selected runtime - smoke Run orchestration behavior smoke checks - resume [--project ] Crash recovery (detect dirty state, generate fix) - -Runtime: - --claude Use Claude runtime hints/prompts (default) - --codex Use Codex runtime hints/prompts - --pi Use Pi runtime hints/prompts - --yolo Launch runtime in dangerous/skip-permissions mode (run only) - -Examples: - mosaic coord init --name "Security Fix" --milestones "Critical,High,Medium" - mosaic coord mission - mosaic coord --codex mission - mosaic coord --pi run - mosaic coord continue --copy - mosaic coord run - mosaic coord run --codex - mosaic coord --yolo run - mosaic coord smoke - mosaic coord continue --codex --copy - -COORD_USAGE - ;; - esac -} - -# Resume advisory — prints warning if active mission or stale session detected -_check_resumable_session() { - local mission_file=".mosaic/orchestrator/mission.json" - local lock_file=".mosaic/orchestrator/session.lock" - - command -v jq &>/dev/null || return 0 - - if [[ -f "$lock_file" ]]; then - local pid - pid="$(jq -r '.pid // 0' "$lock_file" 2>/dev/null)" - if [[ -n "$pid" ]] && [[ "$pid" != "0" ]] && ! kill -0 "$pid" 2>/dev/null; then - # Stale lock from a dead session — clean it up - rm -f "$lock_file" - echo "[mosaic] Cleaned up stale session lock (PID $pid no longer running)." - echo "" - fi - elif [[ -f "$mission_file" ]]; then - local status - status="$(jq -r '.status // "inactive"' "$mission_file" 2>/dev/null)" - if [[ "$status" == "active" ]]; then - echo "[mosaic] Active mission detected. Generate continuation prompt with:" - echo "[mosaic] mosaic coord continue" - echo "" - fi - fi -} - -run_prdy() { - check_mosaic_home - local runtime="claude" - local runtime_flag="" - local -a prdy_args=() - - while [[ $# -gt 0 ]]; do - case "$1" in - --claude|--codex|--pi) - local selected_runtime="${1#--}" - if [[ -n "$runtime_flag" ]] && [[ "$runtime" != "$selected_runtime" ]]; then - echo "[mosaic] ERROR: --claude, --codex, and --pi are mutually exclusive for 'mosaic prdy'." >&2 - exit 1 - fi - runtime="$selected_runtime" - runtime_flag="$1" - shift - ;; - *) - prdy_args+=("$1") - shift - ;; - esac - done - - local subcmd="${prdy_args[0]:-help}" - if (( ${#prdy_args[@]} > 1 )); then - set -- "${prdy_args[@]:1}" - else - set -- - fi - - local tool_dir="$MOSAIC_HOME/tools/prdy" - - case "$subcmd" in - init) - MOSAIC_PRDY_RUNTIME="$runtime" exec bash "$tool_dir/prdy-init.sh" "$@" - ;; - update) - MOSAIC_PRDY_RUNTIME="$runtime" exec bash "$tool_dir/prdy-update.sh" "$@" - ;; - validate|check) - MOSAIC_PRDY_RUNTIME="$runtime" exec bash "$tool_dir/prdy-validate.sh" "$@" - ;; - status) - exec bash "$tool_dir/prdy-status.sh" "$@" - ;; - help|*) - cat <] [--name ] Create docs/PRD.md via guided runtime session - update [--project ] Update existing docs/PRD.md via guided runtime session - validate [--project ] Check PRD completeness against Mosaic guide (bash-only) - status [--project ] [--format short|json] Quick PRD health check (one-liner) - -Runtime: - --claude Use Claude runtime (default) - --codex Use Codex runtime - --pi Use Pi runtime - -Examples: - mosaic prdy init --name "User Authentication" - mosaic prdy update - mosaic prdy --pi init --name "User Authentication" - mosaic prdy --codex init --name "User Authentication" - mosaic prdy validate - -Output location: docs/PRD.md (per Mosaic PRD guide) - -PRDY_USAGE - ;; - esac -} - -run_bootstrap() { - check_mosaic_home - exec "$MOSAIC_HOME/bin/mosaic-bootstrap-repo" "$@" -} - -run_release_upgrade() { - check_mosaic_home - exec "$MOSAIC_HOME/bin/mosaic-release-upgrade" "$@" -} - -run_project_upgrade() { - check_mosaic_home - exec "$MOSAIC_HOME/bin/mosaic-upgrade" "$@" -} - -run_upgrade() { - check_mosaic_home - - # Default: upgrade installed release - if [[ $# -eq 0 ]]; then - run_release_upgrade - fi - - case "$1" in - release) - shift - run_release_upgrade "$@" - ;; - check) - shift - run_release_upgrade --dry-run "$@" - ;; - project) - shift - run_project_upgrade "$@" - ;; - - # Backward compatibility for historical project-upgrade usage. - --all|--root) - run_project_upgrade "$@" - ;; - --dry-run|--ref|--keep|--overwrite|-y|--yes) - run_release_upgrade "$@" - ;; - -*) - run_release_upgrade "$@" - ;; - *) - run_project_upgrade "$@" - ;; - esac -} - -# Main router -if [[ $# -eq 0 ]]; then - usage - exit 0 -fi - -command="$1" -shift - -case "$command" in - pi) launch_pi "$@" ;; - claude) launch_claude "$@" ;; - opencode) launch_opencode "$@" ;; - codex) launch_codex "$@" ;; - yolo|--yolo) launch_yolo "$@" ;; - init) run_init "$@" ;; - doctor) run_doctor "$@" ;; - sync) run_sync "$@" ;; - seq) run_seq "$@" ;; - bootstrap) run_bootstrap "$@" ;; - prdy) run_prdy "$@" ;; - coord) run_coord "$@" ;; - upgrade) run_upgrade "$@" ;; - release-upgrade) run_release_upgrade "$@" ;; - project-upgrade) run_project_upgrade "$@" ;; - help|-h|--help) usage ;; - version|-v|--version) echo "mosaic $VERSION" ;; - *) - echo "[mosaic] Unknown command: $command" >&2 - echo "[mosaic] Run 'mosaic --help' for usage." >&2 - exit 1 - ;; -esac diff --git a/packages/mosaic/framework/bin/mosaic.ps1 b/packages/mosaic/framework/bin/mosaic.ps1 deleted file mode 100644 index 81b7e34..0000000 --- a/packages/mosaic/framework/bin/mosaic.ps1 +++ /dev/null @@ -1,437 +0,0 @@ -# mosaic.ps1 — Unified agent launcher and management CLI (Windows) -# -# AGENTS.md is the global policy source for all agent sessions. -# The launcher injects a composed runtime contract (AGENTS + runtime reference). -# -# Usage: -# mosaic claude [args...] Launch Claude Code with runtime contract injected -# mosaic opencode [args...] Launch OpenCode with runtime contract injected -# mosaic codex [args...] Launch Codex with runtime contract injected -# mosaic yolo [args...] Launch runtime in dangerous-permissions mode -# mosaic --yolo [args...] Alias for yolo -# mosaic init [args...] Generate SOUL.md interactively -# mosaic doctor [args...] Health audit -# mosaic sync [args...] Sync skills -$ErrorActionPreference = "Stop" - -$MosaicHome = if ($env:MOSAIC_HOME) { $env:MOSAIC_HOME } else { Join-Path $env:USERPROFILE ".config\mosaic" } -$Version = "0.1.0" - -function Show-Usage { - Write-Host @" -mosaic $Version - Unified agent launcher - -Usage: mosaic [args...] - -Agent Launchers: - claude [args...] Launch Claude Code with runtime contract injected - opencode [args...] Launch OpenCode with runtime contract injected - codex [args...] Launch Codex with runtime contract injected - yolo [args...] Dangerous mode for claude|codex|opencode - --yolo [args...] Alias for yolo - -Management: - init [args...] Generate SOUL.md (agent identity contract) - doctor [args...] Audit runtime state and detect drift - sync [args...] Sync skills from canonical source - bootstrap Bootstrap a repo with Mosaic standards - upgrade [mode] [args] Upgrade release (default) or project files - upgrade check Check release upgrade status (no changes) - release-upgrade [...] Upgrade installed Mosaic release - project-upgrade [...] Clean up stale SOUL.md/CLAUDE.md in a project - -Options: - -h, --help Show this help - -v, --version Show version -"@ -} - -function Assert-MosaicHome { - if (-not (Test-Path $MosaicHome)) { - Write-Host "[mosaic] ERROR: ~/.config/mosaic not found." -ForegroundColor Red - Write-Host "[mosaic] Install with: bash <(curl -fsSL https://git.mosaicstack.dev/mosaic/mosaic-stack/raw/branch/main/tools/install.sh)" - exit 1 - } -} - -function Assert-AgentsMd { - $agentsPath = Join-Path $MosaicHome "AGENTS.md" - if (-not (Test-Path $agentsPath)) { - Write-Host "[mosaic] ERROR: ~/.config/mosaic/AGENTS.md not found." -ForegroundColor Red - Write-Host "[mosaic] Re-run the installer." - exit 1 - } -} - -function Assert-Soul { - $soulPath = Join-Path $MosaicHome "SOUL.md" - if (-not (Test-Path $soulPath)) { - Write-Host "[mosaic] SOUL.md not found. Running mosaic init..." - & (Join-Path $MosaicHome "bin\mosaic-init.ps1") - } -} - -function Assert-Runtime { - param([string]$Cmd) - if (-not (Get-Command $Cmd -ErrorAction SilentlyContinue)) { - Write-Host "[mosaic] ERROR: '$Cmd' not found in PATH." -ForegroundColor Red - Write-Host "[mosaic] Install $Cmd before launching." - exit 1 - } -} - -function Assert-SequentialThinking { - $checker = Join-Path $MosaicHome "bin\mosaic-ensure-sequential-thinking.ps1" - if (-not (Test-Path $checker)) { - Write-Host "[mosaic] ERROR: sequential-thinking checker missing: $checker" -ForegroundColor Red - exit 1 - } - try { - & $checker -Check *>$null - } - catch { - Write-Host "[mosaic] ERROR: sequential-thinking MCP is required but not configured." -ForegroundColor Red - Write-Host "[mosaic] Run: $checker" - exit 1 - } -} - -function Get-ActiveMission { - $missionFile = Join-Path (Get-Location) ".mosaic\orchestrator\mission.json" - if (-not (Test-Path $missionFile)) { - return $null - } - - try { - $mission = Get-Content $missionFile -Raw | ConvertFrom-Json - } - catch { - return $null - } - - $status = [string]$mission.status - if ([string]::IsNullOrWhiteSpace($status)) { - $status = "inactive" - } - if ($status -ne "active" -and $status -ne "paused") { - return $null - } - - $name = [string]$mission.name - if ([string]::IsNullOrWhiteSpace($name)) { - $name = "unnamed" - } - - $id = [string]$mission.mission_id - if ([string]::IsNullOrWhiteSpace($id)) { - $id = "" - } - - $milestones = @($mission.milestones) - $milestoneCount = $milestones.Count - $milestoneCompleted = @($milestones | Where-Object { $_.status -eq "completed" }).Count - - return [PSCustomObject]@{ - Name = $name - Id = $id - Status = $status - MilestoneCount = $milestoneCount - MilestoneCompleted = $milestoneCompleted - } -} - -function Get-MissionContractBlock { - $mission = Get-ActiveMission - if ($null -eq $mission) { - return "" - } - - return @" -# ACTIVE MISSION — HARD GATE (Read Before Anything Else) - -An active orchestration mission exists in this project. This is a BLOCKING requirement. - -**Mission:** $($mission.Name) -**ID:** $($mission.Id) -**Status:** $($mission.Status) -**Milestones:** $($mission.MilestoneCompleted) / $($mission.MilestoneCount) completed - -## MANDATORY — Before ANY Response to the User - -You MUST complete these steps before responding to any user message, including simple greetings: - -1. Read `~/.config/mosaic/guides/ORCHESTRATOR-PROTOCOL.md` (mission lifecycle protocol) -2. Read `docs/MISSION-MANIFEST.md` for full mission scope, milestones, and success criteria -3. Read the latest scratchpad in `docs/scratchpads/` for session history, decisions, and corrections -4. Read `docs/TASKS.md` for current task state (what is done, what is next) -5. After reading all four, acknowledge the mission state to the user before proceeding - -If the user gives a task, execute it within the mission context. If no task is given, present mission status and ask how to proceed. -"@ -} - -function Get-MissionPrompt { - $mission = Get-ActiveMission - if ($null -eq $mission) { - return "" - } - return "Active mission detected: $($mission.Name). Read the mission state files and report status." -} - -function Get-RuntimePrompt { - param( - [ValidateSet("claude", "codex", "opencode")] - [string]$Runtime - ) - - $runtimeFile = switch ($Runtime) { - "claude" { Join-Path $MosaicHome "runtime\claude\RUNTIME.md" } - "codex" { Join-Path $MosaicHome "runtime\codex\RUNTIME.md" } - "opencode" { Join-Path $MosaicHome "runtime\opencode\RUNTIME.md" } - } - - if (-not (Test-Path $runtimeFile)) { - Write-Host "[mosaic] ERROR: runtime contract not found: $runtimeFile" -ForegroundColor Red - exit 1 - } - - $launcherContract = @' -# Mosaic Launcher Runtime Contract (Hard Gate) - -This contract is injected by `mosaic` launch and is mandatory. - -First assistant response MUST start with exactly one mode declaration line: -1. Orchestration mission: `Now initiating Orchestrator mode...` -2. Implementation mission: `Now initiating Delivery mode...` -3. Review-only mission: `Now initiating Review mode...` - -No tool call or implementation step may occur before that first line. - -Mosaic hard gates OVERRIDE runtime-default caution for routine delivery operations. -For required push/merge/issue-close/release actions, execute without routine confirmation prompts. - -'@ - - $missionBlock = Get-MissionContractBlock - $agentsContent = Get-Content (Join-Path $MosaicHome "AGENTS.md") -Raw - $runtimeContent = Get-Content $runtimeFile -Raw - - if (-not [string]::IsNullOrWhiteSpace($missionBlock)) { - return "$missionBlock`n`n$launcherContract`n$agentsContent`n`n# Runtime-Specific Contract`n`n$runtimeContent" - } - - return "$launcherContract`n$agentsContent`n`n# Runtime-Specific Contract`n`n$runtimeContent" -} - -function Ensure-RuntimeConfig { - param( - [ValidateSet("claude", "codex", "opencode")] - [string]$Runtime, - [string]$Dst - ) - - $parent = Split-Path $Dst -Parent - if (-not (Test-Path $parent)) { New-Item -ItemType Directory -Path $parent -Force | Out-Null } - - $runtimePrompt = Get-RuntimePrompt -Runtime $Runtime - $tmp = [System.IO.Path]::GetTempFileName() - Set-Content -Path $tmp -Value $runtimePrompt -Encoding UTF8 -NoNewline - - $srcHash = (Get-FileHash $tmp -Algorithm SHA256).Hash - $dstHash = if (Test-Path $Dst) { (Get-FileHash $Dst -Algorithm SHA256).Hash } else { "" } - if ($srcHash -ne $dstHash) { - Copy-Item $tmp $Dst -Force - Remove-Item $tmp -Force - } - else { - Remove-Item $tmp -Force - } -} - -function Invoke-Yolo { - param([string[]]$YoloArgs) - - if ($YoloArgs.Count -lt 1) { - Write-Host "[mosaic] ERROR: yolo requires a runtime (claude|codex|opencode)." -ForegroundColor Red - Write-Host "[mosaic] Example: mosaic yolo claude" - exit 1 - } - - $runtime = $YoloArgs[0] - $tail = if ($YoloArgs.Count -gt 1) { @($YoloArgs[1..($YoloArgs.Count - 1)]) } else { @() } - - switch ($runtime) { - "claude" { - Assert-MosaicHome - Assert-AgentsMd - Assert-Soul - Assert-Runtime "claude" - Assert-SequentialThinking - $agentsContent = Get-RuntimePrompt -Runtime "claude" - Write-Host "[mosaic] Launching Claude Code in YOLO mode (dangerous permissions enabled)..." - & claude --dangerously-skip-permissions --append-system-prompt $agentsContent @tail - return - } - "codex" { - Assert-MosaicHome - Assert-AgentsMd - Assert-Soul - Assert-Runtime "codex" - Assert-SequentialThinking - Ensure-RuntimeConfig -Runtime "codex" -Dst (Join-Path $env:USERPROFILE ".codex\instructions.md") - $missionPrompt = Get-MissionPrompt - if (-not [string]::IsNullOrWhiteSpace($missionPrompt) -and $tail.Count -eq 0) { - Write-Host "[mosaic] Launching Codex in YOLO mode (active mission detected)..." - & codex --dangerously-bypass-approvals-and-sandbox $missionPrompt - } - else { - Write-Host "[mosaic] Launching Codex in YOLO mode (dangerous permissions enabled)..." - & codex --dangerously-bypass-approvals-and-sandbox @tail - } - return - } - "opencode" { - Assert-MosaicHome - Assert-AgentsMd - Assert-Soul - Assert-Runtime "opencode" - Assert-SequentialThinking - Ensure-RuntimeConfig -Runtime "opencode" -Dst (Join-Path $env:USERPROFILE ".config\opencode\AGENTS.md") - Write-Host "[mosaic] Launching OpenCode in YOLO mode..." - & opencode @tail - return - } - default { - Write-Host "[mosaic] ERROR: Unsupported yolo runtime '$runtime'. Use claude|codex|opencode." -ForegroundColor Red - exit 1 - } - } -} - -if ($args.Count -eq 0) { - Show-Usage - exit 0 -} - -$command = $args[0] -$remaining = if ($args.Count -gt 1) { @($args[1..($args.Count - 1)]) } else { @() } - -switch ($command) { - "claude" { - Assert-MosaicHome - Assert-AgentsMd - Assert-Soul - Assert-Runtime "claude" - Assert-SequentialThinking - # Claude supports --append-system-prompt for direct injection - $agentsContent = Get-RuntimePrompt -Runtime "claude" - Write-Host "[mosaic] Launching Claude Code..." - & claude --append-system-prompt $agentsContent @remaining - } - "opencode" { - Assert-MosaicHome - Assert-AgentsMd - Assert-Soul - Assert-Runtime "opencode" - Assert-SequentialThinking - # OpenCode reads from ~/.config/opencode/AGENTS.md - Ensure-RuntimeConfig -Runtime "opencode" -Dst (Join-Path $env:USERPROFILE ".config\opencode\AGENTS.md") - Write-Host "[mosaic] Launching OpenCode..." - & opencode @remaining - } - "codex" { - Assert-MosaicHome - Assert-AgentsMd - Assert-Soul - Assert-Runtime "codex" - Assert-SequentialThinking - # Codex reads from ~/.codex/instructions.md - Ensure-RuntimeConfig -Runtime "codex" -Dst (Join-Path $env:USERPROFILE ".codex\instructions.md") - $missionPrompt = Get-MissionPrompt - if (-not [string]::IsNullOrWhiteSpace($missionPrompt) -and $remaining.Count -eq 0) { - Write-Host "[mosaic] Launching Codex (active mission detected)..." - & codex $missionPrompt - } - else { - Write-Host "[mosaic] Launching Codex..." - & codex @remaining - } - } - "yolo" { - Invoke-Yolo -YoloArgs $remaining - } - "--yolo" { - Invoke-Yolo -YoloArgs $remaining - } - "init" { - Assert-MosaicHome - & (Join-Path $MosaicHome "bin\mosaic-init.ps1") @remaining - } - "doctor" { - Assert-MosaicHome - & (Join-Path $MosaicHome "bin\mosaic-doctor.ps1") @remaining - } - "sync" { - Assert-MosaicHome - & (Join-Path $MosaicHome "bin\mosaic-sync-skills.ps1") @remaining - } - "bootstrap" { - Assert-MosaicHome - Write-Host "[mosaic] NOTE: mosaic-bootstrap-repo requires bash. Use Git Bash or WSL." -ForegroundColor Yellow - & (Join-Path $MosaicHome "bin\mosaic-bootstrap-repo") @remaining - } - "upgrade" { - Assert-MosaicHome - if ($remaining.Count -eq 0) { - & (Join-Path $MosaicHome "bin\mosaic-release-upgrade.ps1") - break - } - - $mode = $remaining[0] - $tail = if ($remaining.Count -gt 1) { $remaining[1..($remaining.Count - 1)] } else { @() } - - switch -Regex ($mode) { - "^release$" { - & (Join-Path $MosaicHome "bin\mosaic-release-upgrade.ps1") @tail - } - "^check$" { - & (Join-Path $MosaicHome "bin\mosaic-release-upgrade.ps1") -DryRun @tail - } - "^project$" { - Write-Host "[mosaic] NOTE: mosaic-upgrade requires bash. Use Git Bash or WSL." -ForegroundColor Yellow - & (Join-Path $MosaicHome "bin\mosaic-upgrade") @tail - } - "^(--all|--root)$" { - Write-Host "[mosaic] NOTE: mosaic-upgrade requires bash. Use Git Bash or WSL." -ForegroundColor Yellow - & (Join-Path $MosaicHome "bin\mosaic-upgrade") @remaining - } - "^(--dry-run|--ref|--keep|--overwrite|-y|--yes)$" { - & (Join-Path $MosaicHome "bin\mosaic-release-upgrade.ps1") @remaining - } - "^-.*" { - & (Join-Path $MosaicHome "bin\mosaic-release-upgrade.ps1") @remaining - } - default { - Write-Host "[mosaic] NOTE: treating positional argument as project path." -ForegroundColor Yellow - Write-Host "[mosaic] NOTE: mosaic-upgrade requires bash. Use Git Bash or WSL." -ForegroundColor Yellow - & (Join-Path $MosaicHome "bin\mosaic-upgrade") @remaining - } - } - } - "release-upgrade" { - Assert-MosaicHome - & (Join-Path $MosaicHome "bin\mosaic-release-upgrade.ps1") @remaining - } - "project-upgrade" { - Assert-MosaicHome - Write-Host "[mosaic] NOTE: mosaic-upgrade requires bash. Use Git Bash or WSL." -ForegroundColor Yellow - & (Join-Path $MosaicHome "bin\mosaic-upgrade") @remaining - } - { $_ -in "help", "-h", "--help" } { Show-Usage } - { $_ -in "version", "-v", "--version" } { Write-Host "mosaic $Version" } - default { - Write-Host "[mosaic] Unknown command: $command" -ForegroundColor Red - Write-Host "[mosaic] Run 'mosaic --help' for usage." - exit 1 - } -} diff --git a/packages/mosaic/framework/install.sh b/packages/mosaic/framework/install.sh index aae05be..051441c 100755 --- a/packages/mosaic/framework/install.sh +++ b/packages/mosaic/framework/install.sh @@ -1,12 +1,32 @@ #!/usr/bin/env bash set -euo pipefail +# ─── Mosaic Framework Installer ────────────────────────────────────────────── +# +# Installs/upgrades the framework DATA to ~/.config/mosaic/. +# No executables are placed on PATH — the mosaic npm CLI is the only binary. +# +# Called by tools/install.sh (the unified installer). Can also be run directly. +# +# Environment: +# 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 +# ────────────────────────────────────────────────────────────────────────────── + SOURCE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" TARGET_DIR="${MOSAIC_HOME:-$HOME/.config/mosaic}" -INSTALL_MODE="${MOSAIC_INSTALL_MODE:-prompt}" # prompt|keep|overwrite -PRESERVE_PATHS=("SOUL.md" "USER.md" "TOOLS.md" "memory") +INSTALL_MODE="${MOSAIC_INSTALL_MODE:-prompt}" -# Colors (disabled if not a terminal) +# Files preserved across upgrades (never overwritten) +PRESERVE_PATHS=("SOUL.md" "USER.md" "TOOLS.md" "memory" "sources") + +# Current framework schema version — bump this when the layout changes. +# The migration system uses this to run upgrade steps. +FRAMEWORK_VERSION=2 + +# ─── colours ────────────────────────────────────────────────────────────────── if [[ -t 1 ]]; then GREEN='\033[0;32m' YELLOW='\033[0;33m' RED='\033[0;31m' CYAN='\033[0;36m' BOLD='\033[1m' RESET='\033[0m' @@ -19,9 +39,29 @@ warn() { echo -e " ${YELLOW}⚠${RESET} $1" >&2; } fail() { echo -e " ${RED}✗${RESET} $1" >&2; } step() { echo -e "\n${BOLD}$1${RESET}"; } +# ─── helpers ────────────────────────────────────────────────────────────────── + is_existing_install() { [[ -d "$TARGET_DIR" ]] || return 1 - [[ -f "$TARGET_DIR/bin/mosaic" || -f "$TARGET_DIR/AGENTS.md" || -f "$TARGET_DIR/SOUL.md" ]] + [[ -f "$TARGET_DIR/AGENTS.md" || -f "$TARGET_DIR/SOUL.md" ]] +} + +installed_framework_version() { + local vf="$TARGET_DIR/.framework-version" + if [[ -f "$vf" ]]; then + cat "$vf" 2>/dev/null || echo "0" + else + # No version file = legacy install (version 0 or 1) + if [[ -d "$TARGET_DIR/bin" ]]; then + echo "1" # Has bin/ → pre-migration legacy + else + echo "0" # Fresh or unknown + fi + fi +} + +write_framework_version() { + echo "$FRAMEWORK_VERSION" > "$TARGET_DIR/.framework-version" } select_install_mode() { @@ -39,33 +79,22 @@ select_install_mode() { fi case "$INSTALL_MODE" in - keep|overwrite) - ;; + keep|overwrite) ;; prompt) if [[ -t 0 ]]; then echo "" echo "Existing Mosaic install detected at: $TARGET_DIR" - echo "Choose reinstall mode:" - echo " 1) keep Keep local files (SOUL.md, USER.md, TOOLS.md, memory/) while updating framework" - echo " 2) overwrite Replace everything in $TARGET_DIR" - echo " 3) cancel Abort install" + echo " 1) keep Update framework, preserve local files (SOUL.md, USER.md, etc.)" + echo " 2) overwrite Replace everything" + echo " 3) cancel Abort" printf "Selection [1/2/3] (default: 1): " read -r selection - case "${selection:-1}" in - 1|k|K|keep|KEEP) INSTALL_MODE="keep" ;; - 2|o|O|overwrite|OVERWRITE) INSTALL_MODE="overwrite" ;; - 3|c|C|cancel|CANCEL|n|N|no|NO) - fail "Install cancelled." - exit 1 - ;; - *) - warn "Unrecognized selection '$selection'; defaulting to keep." - INSTALL_MODE="keep" - ;; + 1|k|K|keep) INSTALL_MODE="keep" ;; + 2|o|O|overwrite) INSTALL_MODE="overwrite" ;; + *) fail "Install cancelled."; exit 1 ;; esac else - warn "Existing install detected without interactive input; defaulting to keep local files." INSTALL_MODE="keep" fi ;; @@ -83,10 +112,9 @@ sync_framework() { fi if command -v rsync >/dev/null 2>&1; then - local rsync_args=(-a --delete --exclude ".git") + local rsync_args=(-a --delete --exclude ".git" --exclude ".framework-version") if [[ "$INSTALL_MODE" == "keep" ]]; then - local path for path in "${PRESERVE_PATHS[@]}"; do rsync_args+=(--exclude "$path") done @@ -96,10 +124,10 @@ sync_framework() { return fi + # Fallback: cp-based sync local preserve_tmp="" if [[ "$INSTALL_MODE" == "keep" ]]; then preserve_tmp="$(mktemp -d "${TMPDIR:-/tmp}/mosaic-preserve-XXXXXX")" - local path for path in "${PRESERVE_PATHS[@]}"; do if [[ -e "$TARGET_DIR/$path" ]]; then mkdir -p "$preserve_tmp/$(dirname "$path")" @@ -108,12 +136,11 @@ sync_framework() { done fi - find "$TARGET_DIR" -mindepth 1 -maxdepth 1 ! -name ".git" -exec rm -rf {} + + find "$TARGET_DIR" -mindepth 1 -maxdepth 1 ! -name ".git" ! -name ".framework-version" -exec rm -rf {} + cp -R "$SOURCE_DIR"/. "$TARGET_DIR"/ rm -rf "$TARGET_DIR/.git" if [[ -n "$preserve_tmp" ]]; then - local path for path in "${PRESERVE_PATHS[@]}"; do if [[ -e "$preserve_tmp/$path" ]]; then rm -rf "$TARGET_DIR/$path" @@ -125,136 +152,133 @@ sync_framework() { fi } +# ═══════════════════════════════════════════════════════════════════════════════ +# Migrations — run sequentially from the installed version to FRAMEWORK_VERSION +# ═══════════════════════════════════════════════════════════════════════════════ + +run_migrations() { + local from_version + from_version="$(installed_framework_version)" + + if [[ "$from_version" -ge "$FRAMEWORK_VERSION" ]]; then + return # Already current + fi + + step "Running migrations (v${from_version} → v${FRAMEWORK_VERSION})" + + # ── Migration: v0/v1 → v2 ───────────────────────────────────────────────── + # Remove bin/ directory — all executables now live in the npm CLI. + # Scripts that were in bin/ are now in tools/_scripts/. + if [[ "$from_version" -lt 2 ]]; then + if [[ -d "$TARGET_DIR/bin" ]]; then + ok "Removing legacy bin/ directory (executables now in npm CLI)" + rm -rf "$TARGET_DIR/bin" + fi + + # Remove old mosaic PATH entry from shell profiles + for profile in "$HOME/.bashrc" "$HOME/.zshrc" "$HOME/.profile"; do + if [[ -f "$profile" ]] && grep -qF "$TARGET_DIR/bin" "$profile"; then + # Remove the PATH line and the comment above it + sed -i.mosaic-migration-bak \ + -e "\|# Mosaic agent framework|d" \ + -e "\|$TARGET_DIR/bin|d" \ + "$profile" + ok "Cleaned up old PATH entry from $(basename "$profile")" + rm -f "${profile}.mosaic-migration-bak" + fi + done + + # Remove stale rails/ symlink + if [[ -L "$TARGET_DIR/rails" ]]; then + rm -f "$TARGET_DIR/rails" + fi + fi + + # ── Future migrations go here ────────────────────────────────────────────── + # if [[ "$from_version" -lt 3 ]]; then + # ... + # fi +} + +# ═══════════════════════════════════════════════════════════════════════════════ +# Main +# ═══════════════════════════════════════════════════════════════════════════════ + step "Installing Mosaic framework" mkdir -p "$TARGET_DIR" select_install_mode if [[ "$INSTALL_MODE" == "keep" ]]; then - ok "Install mode: keep local SOUL.md/USER.md/TOOLS.md/memory while updating framework" + ok "Install mode: keep local files (SOUL.md, USER.md, TOOLS.md, memory/)" else - ok "Install mode: overwrite existing files" + ok "Install mode: overwrite" fi sync_framework -# Ensure memory directory exists (preserved across upgrades, may not exist on fresh install) +# Ensure memory directory exists mkdir -p "$TARGET_DIR/memory" -chmod +x "$TARGET_DIR"/bin/* -chmod +x "$TARGET_DIR"/install.sh - # Ensure tool scripts are executable find "$TARGET_DIR/tools" -name "*.sh" -exec chmod +x {} + 2>/dev/null || true +find "$TARGET_DIR/tools/_scripts" -type f -exec chmod +x {} + 2>/dev/null || true -# Create backward-compat symlink: rails/ → tools/ -if [[ -d "$TARGET_DIR/tools" ]]; then - if [[ -d "$TARGET_DIR/rails" ]] && [[ ! -L "$TARGET_DIR/rails" ]]; then - rm -rf "$TARGET_DIR/rails" - fi - ln -sfn "tools" "$TARGET_DIR/rails" -fi +ok "Framework synced to $TARGET_DIR" -ok "Framework installed to $TARGET_DIR" +# Run migrations before post-install (migrations may remove old bin/ etc.) +run_migrations step "Post-install tasks" -if "$TARGET_DIR/bin/mosaic-link-runtime-assets" >/dev/null 2>&1; then - ok "Runtime assets linked" -else - warn "Runtime asset linking failed (non-fatal)" -fi +SCRIPTS="$TARGET_DIR/tools/_scripts" -if "$TARGET_DIR/bin/mosaic-ensure-sequential-thinking" >/dev/null 2>&1; then - ok "sequential-thinking MCP configured" -else - if [[ "${MOSAIC_ALLOW_MISSING_SEQUENTIAL_THINKING:-0}" == "1" ]]; then - warn "sequential-thinking MCP setup failed but bypassed (MOSAIC_ALLOW_MISSING_SEQUENTIAL_THINKING=1)" +if [[ -x "$SCRIPTS/mosaic-link-runtime-assets" ]]; then + if "$SCRIPTS/mosaic-link-runtime-assets" >/dev/null 2>&1; then + ok "Runtime assets linked" else - fail "sequential-thinking MCP setup failed (hard requirement)." - fail "Set MOSAIC_ALLOW_MISSING_SEQUENTIAL_THINKING=1 only for temporary bypass scenarios." - exit 1 + warn "Runtime asset linking failed (non-fatal)" fi fi -if "$TARGET_DIR/bin/mosaic-ensure-excalidraw" >/dev/null 2>&1; then - ok "excalidraw MCP configured" -else - warn "excalidraw MCP setup failed (non-fatal) — run 'mosaic-ensure-excalidraw' to retry" -fi - -if [[ "${MOSAIC_SKIP_SKILLS_SYNC:-0}" == "1" ]]; then - ok "Skills sync skipped (MOSAIC_SKIP_SKILLS_SYNC=1)" -else - if "$TARGET_DIR/bin/mosaic-sync-skills" >/dev/null 2>&1; then - ok "Skills synced" +if [[ -x "$SCRIPTS/mosaic-ensure-sequential-thinking" ]]; then + if "$SCRIPTS/mosaic-ensure-sequential-thinking" >/dev/null 2>&1; then + ok "sequential-thinking MCP configured" else - warn "Skills sync failed (non-fatal)" + if [[ "${MOSAIC_ALLOW_MISSING_SEQUENTIAL_THINKING:-0}" == "1" ]]; then + warn "sequential-thinking MCP setup bypassed (MOSAIC_ALLOW_MISSING_SEQUENTIAL_THINKING=1)" + else + fail "sequential-thinking MCP setup failed (hard requirement)." + exit 1 + fi fi fi -if "$TARGET_DIR/bin/mosaic-migrate-local-skills" --apply >/dev/null 2>&1; then - ok "Local skills migrated" -else - warn "Local skill migration failed (non-fatal)" +if [[ -x "$SCRIPTS/mosaic-ensure-excalidraw" ]]; then + "$SCRIPTS/mosaic-ensure-excalidraw" >/dev/null 2>&1 && ok "excalidraw MCP configured" || warn "excalidraw MCP setup failed (non-fatal)" fi -if "$TARGET_DIR/bin/mosaic-doctor" >/dev/null 2>&1; then - ok "Health audit passed" -else - warn "Health audit reported issues — run 'mosaic doctor' for details" +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)" fi -step "PATH configuration" - -PATH_LINE="export PATH=\"$TARGET_DIR/bin:\$PATH\"" - -# Find the right shell profile -if [[ -n "${ZSH_VERSION:-}" ]] || [[ "$(basename "${SHELL:-}")" == "zsh" ]]; then - SHELL_PROFILE="$HOME/.zshrc" -elif [[ -f "$HOME/.bashrc" ]]; then - SHELL_PROFILE="$HOME/.bashrc" -elif [[ -f "$HOME/.profile" ]]; then - SHELL_PROFILE="$HOME/.profile" -else - SHELL_PROFILE="$HOME/.profile" +if [[ -x "$SCRIPTS/mosaic-migrate-local-skills" ]]; then + "$SCRIPTS/mosaic-migrate-local-skills" --apply >/dev/null 2>&1 && ok "Local skills migrated" || warn "Local skill migration failed (non-fatal)" fi -PATH_CHANGED=false -if grep -qF "$TARGET_DIR/bin" "$SHELL_PROFILE" 2>/dev/null; then - ok "Already in PATH via $SHELL_PROFILE" -else - { - echo "" - echo "# Mosaic agent framework" - echo "$PATH_LINE" - } >> "$SHELL_PROFILE" - ok "Added to PATH in $SHELL_PROFILE" - PATH_CHANGED=true +if [[ -x "$SCRIPTS/mosaic-doctor" ]]; then + "$SCRIPTS/mosaic-doctor" >/dev/null 2>&1 && ok "Health audit passed" || warn "Health audit reported issues — run 'mosaic doctor' for details" fi +# Write version stamp AFTER everything succeeds +write_framework_version + # ── Summary ────────────────────────────────────────────────── echo "" -echo -e "${GREEN}${BOLD} Mosaic installed successfully.${RESET}" +echo -e "${GREEN}${BOLD} Mosaic framework installed.${RESET}" echo "" -# Collect next steps -NEXT_STEPS=() - -if [[ "$PATH_CHANGED" == "true" ]]; then - NEXT_STEPS+=("Run ${CYAN}source $SHELL_PROFILE${RESET} or log out and back in to activate PATH.") -fi - if [[ ! -f "$TARGET_DIR/SOUL.md" ]]; then - NEXT_STEPS+=("Run ${CYAN}mosaic init${RESET} to set up your agent identity (SOUL.md), user profile (USER.md), and tool config (TOOLS.md).") -elif grep -q "not configured" "$TARGET_DIR/USER.md" 2>/dev/null; then - NEXT_STEPS+=("Run ${CYAN}mosaic init${RESET} to personalize your user profile (USER.md) and tool config (TOOLS.md).") -fi - -if [[ ${#NEXT_STEPS[@]} -gt 0 ]]; then - echo -e " ${BOLD}Next steps:${RESET}" - for i in "${!NEXT_STEPS[@]}"; do - echo -e " $((i+1)). ${NEXT_STEPS[$i]}" - done + echo -e " Run ${CYAN}mosaic init${RESET} to set up your agent identity." echo "" fi diff --git a/packages/mosaic/framework/templates/repo/scripts/agent/orchestrator-daemon.sh b/packages/mosaic/framework/templates/repo/scripts/agent/orchestrator-daemon.sh index 73c29ba..eea1056 100755 --- a/packages/mosaic/framework/templates/repo/scripts/agent/orchestrator-daemon.sh +++ b/packages/mosaic/framework/templates/repo/scripts/agent/orchestrator-daemon.sh @@ -69,12 +69,12 @@ case "$cmd" in echo "[agent-framework] orchestrator already running (pid=$(cat "$PID_FILE"))" exit 0 fi - nohup "$MOSAIC_HOME/bin/mosaic-orchestrator-drain" --poll-sec "$poll_sec" $sync_arg >"$LOG_FILE" 2>&1 & + nohup "$MOSAIC_HOME/tools/_scripts/mosaic-orchestrator-drain" --poll-sec "$poll_sec" $sync_arg >"$LOG_FILE" 2>&1 & echo "$!" > "$PID_FILE" echo "[agent-framework] orchestrator started (pid=$!, log=$LOG_FILE)" ;; drain) - exec "$MOSAIC_HOME/bin/mosaic-orchestrator-drain" --poll-sec "$poll_sec" $sync_arg + exec "$MOSAIC_HOME/tools/_scripts/mosaic-orchestrator-drain" --poll-sec "$poll_sec" $sync_arg ;; stop) if ! is_running; then diff --git a/packages/mosaic/framework/bin/mosaic-bootstrap-repo b/packages/mosaic/framework/tools/_scripts/mosaic-bootstrap-repo similarity index 95% rename from packages/mosaic/framework/bin/mosaic-bootstrap-repo rename to packages/mosaic/framework/tools/_scripts/mosaic-bootstrap-repo index b24b0b8..8ab5927 100755 --- a/packages/mosaic/framework/bin/mosaic-bootstrap-repo +++ b/packages/mosaic/framework/tools/_scripts/mosaic-bootstrap-repo @@ -113,8 +113,8 @@ echo "[mosaic] Optional: run orchestrator rail via ~/.config/mosaic/bin/mosaic-o echo "[mosaic] Optional: run detached orchestrator via bash $TARGET_DIR/scripts/agent/orchestrator-daemon.sh start" if [[ -n "$QUALITY_TEMPLATE" ]]; then - if [[ -x "$MOSAIC_HOME/bin/mosaic-quality-apply" ]]; then - "$MOSAIC_HOME/bin/mosaic-quality-apply" --template "$QUALITY_TEMPLATE" --target "$TARGET_DIR" + if [[ -x "$MOSAIC_HOME/tools/_scripts/mosaic-quality-apply" ]]; then + "$MOSAIC_HOME/tools/_scripts/mosaic-quality-apply" --template "$QUALITY_TEMPLATE" --target "$TARGET_DIR" if [[ -f "$TARGET_DIR/.mosaic/quality-rails.yml" ]]; then sed -i "s/^enabled:.*/enabled: true/" "$TARGET_DIR/.mosaic/quality-rails.yml" sed -i "s/^template:.*/template: \"$QUALITY_TEMPLATE\"/" "$TARGET_DIR/.mosaic/quality-rails.yml" diff --git a/packages/mosaic/framework/bin/mosaic-clean-runtime b/packages/mosaic/framework/tools/_scripts/mosaic-clean-runtime similarity index 100% rename from packages/mosaic/framework/bin/mosaic-clean-runtime rename to packages/mosaic/framework/tools/_scripts/mosaic-clean-runtime diff --git a/packages/mosaic/framework/bin/mosaic-critical b/packages/mosaic/framework/tools/_scripts/mosaic-critical similarity index 100% rename from packages/mosaic/framework/bin/mosaic-critical rename to packages/mosaic/framework/tools/_scripts/mosaic-critical diff --git a/packages/mosaic/framework/bin/mosaic-doctor b/packages/mosaic/framework/tools/_scripts/mosaic-doctor similarity index 91% rename from packages/mosaic/framework/bin/mosaic-doctor rename to packages/mosaic/framework/tools/_scripts/mosaic-doctor index 3b81f74..4a7466c 100755 --- a/packages/mosaic/framework/bin/mosaic-doctor +++ b/packages/mosaic/framework/tools/_scripts/mosaic-doctor @@ -165,18 +165,18 @@ expect_dir "$MOSAIC_HOME/profiles" expect_dir "$MOSAIC_HOME/templates/agent" expect_dir "$MOSAIC_HOME/skills" expect_dir "$MOSAIC_HOME/skills-local" -expect_file "$MOSAIC_HOME/bin/mosaic-link-runtime-assets" -expect_file "$MOSAIC_HOME/bin/mosaic-ensure-sequential-thinking" -expect_file "$MOSAIC_HOME/bin/mosaic-sync-skills" -expect_file "$MOSAIC_HOME/bin/mosaic-projects" -expect_file "$MOSAIC_HOME/bin/mosaic-quality-apply" -expect_file "$MOSAIC_HOME/bin/mosaic-quality-verify" -expect_file "$MOSAIC_HOME/bin/mosaic-orchestrator-run" -expect_file "$MOSAIC_HOME/bin/mosaic-orchestrator-sync-tasks" -expect_file "$MOSAIC_HOME/bin/mosaic-orchestrator-drain" -expect_file "$MOSAIC_HOME/bin/mosaic-orchestrator-matrix-publish" -expect_file "$MOSAIC_HOME/bin/mosaic-orchestrator-matrix-consume" -expect_file "$MOSAIC_HOME/bin/mosaic-orchestrator-matrix-cycle" +expect_file "$MOSAIC_HOME/tools/_scripts/mosaic-link-runtime-assets" +expect_file "$MOSAIC_HOME/tools/_scripts/mosaic-ensure-sequential-thinking" +expect_file "$MOSAIC_HOME/tools/_scripts/mosaic-sync-skills" +expect_file "$MOSAIC_HOME/tools/_scripts/mosaic-projects" +expect_file "$MOSAIC_HOME/tools/_scripts/mosaic-quality-apply" +expect_file "$MOSAIC_HOME/tools/_scripts/mosaic-quality-verify" +expect_file "$MOSAIC_HOME/tools/_scripts/mosaic-orchestrator-run" +expect_file "$MOSAIC_HOME/tools/_scripts/mosaic-orchestrator-sync-tasks" +expect_file "$MOSAIC_HOME/tools/_scripts/mosaic-orchestrator-drain" +expect_file "$MOSAIC_HOME/tools/_scripts/mosaic-orchestrator-matrix-publish" +expect_file "$MOSAIC_HOME/tools/_scripts/mosaic-orchestrator-matrix-consume" +expect_file "$MOSAIC_HOME/tools/_scripts/mosaic-orchestrator-matrix-cycle" expect_file "$MOSAIC_HOME/tools/git/ci-queue-wait.sh" expect_file "$MOSAIC_HOME/tools/git/pr-ci-wait.sh" expect_file "$MOSAIC_HOME/tools/orchestrator-matrix/transport/matrix_transport.py" @@ -215,8 +215,8 @@ check_runtime_contract_file "$HOME/.config/opencode/AGENTS.md" "$MOSAIC_HOME/run check_runtime_contract_file "$HOME/.codex/instructions.md" "$MOSAIC_HOME/runtime/codex/instructions.md" "codex" # Sequential-thinking MCP hard requirement. -if [[ -x "$MOSAIC_HOME/bin/mosaic-ensure-sequential-thinking" ]]; then - if "$MOSAIC_HOME/bin/mosaic-ensure-sequential-thinking" --check >/dev/null 2>&1; then +if [[ -x "$MOSAIC_HOME/tools/_scripts/mosaic-ensure-sequential-thinking" ]]; then + if "$MOSAIC_HOME/tools/_scripts/mosaic-ensure-sequential-thinking" --check >/dev/null 2>&1; then pass "sequential-thinking MCP configured and available" else warn "sequential-thinking MCP missing or misconfigured" @@ -422,8 +422,8 @@ with open('$pi_settings_file', 'w') as f: fi # 4. Run link-runtime-assets if available - if [[ -x "$MOSAIC_HOME/bin/mosaic-link-runtime-assets" ]]; then - "$MOSAIC_HOME/bin/mosaic-link-runtime-assets" >/dev/null 2>&1 && fix "Re-ran mosaic-link-runtime-assets" + if [[ -x "$MOSAIC_HOME/tools/_scripts/mosaic-link-runtime-assets" ]]; then + "$MOSAIC_HOME/tools/_scripts/mosaic-link-runtime-assets" >/dev/null 2>&1 && fix "Re-ran mosaic-link-runtime-assets" fi echo "[mosaic-doctor] fixes=$fix_count" diff --git a/packages/mosaic/framework/bin/mosaic-doctor.ps1 b/packages/mosaic/framework/tools/_scripts/mosaic-doctor.ps1 similarity index 100% rename from packages/mosaic/framework/bin/mosaic-doctor.ps1 rename to packages/mosaic/framework/tools/_scripts/mosaic-doctor.ps1 diff --git a/packages/mosaic/framework/bin/mosaic-ensure-excalidraw b/packages/mosaic/framework/tools/_scripts/mosaic-ensure-excalidraw similarity index 100% rename from packages/mosaic/framework/bin/mosaic-ensure-excalidraw rename to packages/mosaic/framework/tools/_scripts/mosaic-ensure-excalidraw diff --git a/packages/mosaic/framework/bin/mosaic-ensure-sequential-thinking b/packages/mosaic/framework/tools/_scripts/mosaic-ensure-sequential-thinking similarity index 100% rename from packages/mosaic/framework/bin/mosaic-ensure-sequential-thinking rename to packages/mosaic/framework/tools/_scripts/mosaic-ensure-sequential-thinking diff --git a/packages/mosaic/framework/bin/mosaic-ensure-sequential-thinking.ps1 b/packages/mosaic/framework/tools/_scripts/mosaic-ensure-sequential-thinking.ps1 similarity index 100% rename from packages/mosaic/framework/bin/mosaic-ensure-sequential-thinking.ps1 rename to packages/mosaic/framework/tools/_scripts/mosaic-ensure-sequential-thinking.ps1 diff --git a/packages/mosaic/framework/bin/mosaic-init b/packages/mosaic/framework/tools/_scripts/mosaic-init similarity index 98% rename from packages/mosaic/framework/bin/mosaic-init rename to packages/mosaic/framework/tools/_scripts/mosaic-init index f61a20f..3c0b6f7 100755 --- a/packages/mosaic/framework/bin/mosaic-init +++ b/packages/mosaic/framework/tools/_scripts/mosaic-init @@ -261,9 +261,9 @@ echo "[mosaic-init] Style: $STYLE" if [[ $SOUL_ONLY -eq 1 ]]; then # Push to runtime adapters and exit - if [[ -x "$MOSAIC_HOME/bin/mosaic-link-runtime-assets" ]]; then + if [[ -x "$MOSAIC_HOME/tools/_scripts/mosaic-link-runtime-assets" ]]; then echo "[mosaic-init] Updating runtime adapters..." - "$MOSAIC_HOME/bin/mosaic-link-runtime-assets" + "$MOSAIC_HOME/tools/_scripts/mosaic-link-runtime-assets" fi echo "[mosaic-init] Done. Launch with: mosaic claude" exit 0 @@ -413,10 +413,10 @@ fi # ── Finalize ────────────────────────────────────────────────── # Push to runtime adapters -if [[ -x "$MOSAIC_HOME/bin/mosaic-link-runtime-assets" ]]; then +if [[ -x "$MOSAIC_HOME/tools/_scripts/mosaic-link-runtime-assets" ]]; then echo "" echo "[mosaic-init] Updating runtime adapters..." - "$MOSAIC_HOME/bin/mosaic-link-runtime-assets" + "$MOSAIC_HOME/tools/_scripts/mosaic-link-runtime-assets" fi echo "" diff --git a/packages/mosaic/framework/bin/mosaic-init.ps1 b/packages/mosaic/framework/tools/_scripts/mosaic-init.ps1 similarity index 100% rename from packages/mosaic/framework/bin/mosaic-init.ps1 rename to packages/mosaic/framework/tools/_scripts/mosaic-init.ps1 diff --git a/packages/mosaic/framework/bin/mosaic-link-runtime-assets b/packages/mosaic/framework/tools/_scripts/mosaic-link-runtime-assets similarity index 96% rename from packages/mosaic/framework/bin/mosaic-link-runtime-assets rename to packages/mosaic/framework/tools/_scripts/mosaic-link-runtime-assets index 0f25135..9bf2a9e 100755 --- a/packages/mosaic/framework/bin/mosaic-link-runtime-assets +++ b/packages/mosaic/framework/tools/_scripts/mosaic-link-runtime-assets @@ -128,8 +128,8 @@ fi # Pi extension is loaded via --extension flag in the mosaic launcher. # Do NOT copy into ~/.pi/agent/extensions/ — that causes duplicate loading. -if [[ -x "$MOSAIC_HOME/bin/mosaic-ensure-sequential-thinking" ]]; then - "$MOSAIC_HOME/bin/mosaic-ensure-sequential-thinking" +if [[ -x "$MOSAIC_HOME/tools/_scripts/mosaic-ensure-sequential-thinking" ]]; then + "$MOSAIC_HOME/tools/_scripts/mosaic-ensure-sequential-thinking" fi echo "[mosaic-link] Runtime assets synced (non-symlink mode)" diff --git a/packages/mosaic/framework/bin/mosaic-link-runtime-assets.ps1 b/packages/mosaic/framework/tools/_scripts/mosaic-link-runtime-assets.ps1 similarity index 100% rename from packages/mosaic/framework/bin/mosaic-link-runtime-assets.ps1 rename to packages/mosaic/framework/tools/_scripts/mosaic-link-runtime-assets.ps1 diff --git a/packages/mosaic/framework/bin/mosaic-log-limitation b/packages/mosaic/framework/tools/_scripts/mosaic-log-limitation similarity index 100% rename from packages/mosaic/framework/bin/mosaic-log-limitation rename to packages/mosaic/framework/tools/_scripts/mosaic-log-limitation diff --git a/packages/mosaic/framework/bin/mosaic-migrate-local-skills b/packages/mosaic/framework/tools/_scripts/mosaic-migrate-local-skills similarity index 100% rename from packages/mosaic/framework/bin/mosaic-migrate-local-skills rename to packages/mosaic/framework/tools/_scripts/mosaic-migrate-local-skills diff --git a/packages/mosaic/framework/bin/mosaic-migrate-local-skills.ps1 b/packages/mosaic/framework/tools/_scripts/mosaic-migrate-local-skills.ps1 similarity index 100% rename from packages/mosaic/framework/bin/mosaic-migrate-local-skills.ps1 rename to packages/mosaic/framework/tools/_scripts/mosaic-migrate-local-skills.ps1 diff --git a/packages/mosaic/framework/bin/mosaic-orchestrator-drain b/packages/mosaic/framework/tools/_scripts/mosaic-orchestrator-drain similarity index 78% rename from packages/mosaic/framework/bin/mosaic-orchestrator-drain rename to packages/mosaic/framework/tools/_scripts/mosaic-orchestrator-drain index 5900dde..837c45c 100755 --- a/packages/mosaic/framework/bin/mosaic-orchestrator-drain +++ b/packages/mosaic/framework/tools/_scripts/mosaic-orchestrator-drain @@ -2,8 +2,8 @@ set -euo pipefail MOSAIC_HOME="${MOSAIC_HOME:-$HOME/.config/mosaic}" -sync_cmd="$MOSAIC_HOME/bin/mosaic-orchestrator-sync-tasks" -run_cmd="$MOSAIC_HOME/bin/mosaic-orchestrator-run" +sync_cmd="$MOSAIC_HOME/tools/_scripts/mosaic-orchestrator-sync-tasks" +run_cmd="$MOSAIC_HOME/tools/_scripts/mosaic-orchestrator-run" do_sync=1 poll_sec=15 diff --git a/packages/mosaic/framework/bin/mosaic-orchestrator-matrix-consume b/packages/mosaic/framework/tools/_scripts/mosaic-orchestrator-matrix-consume similarity index 100% rename from packages/mosaic/framework/bin/mosaic-orchestrator-matrix-consume rename to packages/mosaic/framework/tools/_scripts/mosaic-orchestrator-matrix-consume diff --git a/packages/mosaic/framework/bin/mosaic-orchestrator-matrix-cycle b/packages/mosaic/framework/tools/_scripts/mosaic-orchestrator-matrix-cycle similarity index 58% rename from packages/mosaic/framework/bin/mosaic-orchestrator-matrix-cycle rename to packages/mosaic/framework/tools/_scripts/mosaic-orchestrator-matrix-cycle index aa04896..b8f04ce 100755 --- a/packages/mosaic/framework/bin/mosaic-orchestrator-matrix-cycle +++ b/packages/mosaic/framework/tools/_scripts/mosaic-orchestrator-matrix-cycle @@ -3,9 +3,9 @@ set -euo pipefail MOSAIC_HOME="${MOSAIC_HOME:-$HOME/.config/mosaic}" -consume="$MOSAIC_HOME/bin/mosaic-orchestrator-matrix-consume" -run="$MOSAIC_HOME/bin/mosaic-orchestrator-run" -publish="$MOSAIC_HOME/bin/mosaic-orchestrator-matrix-publish" +consume="$MOSAIC_HOME/tools/_scripts/mosaic-orchestrator-matrix-consume" +run="$MOSAIC_HOME/tools/_scripts/mosaic-orchestrator-run" +publish="$MOSAIC_HOME/tools/_scripts/mosaic-orchestrator-matrix-publish" for cmd in "$consume" "$run" "$publish"; do if [[ ! -x "$cmd" ]]; then diff --git a/packages/mosaic/framework/bin/mosaic-orchestrator-matrix-publish b/packages/mosaic/framework/tools/_scripts/mosaic-orchestrator-matrix-publish similarity index 100% rename from packages/mosaic/framework/bin/mosaic-orchestrator-matrix-publish rename to packages/mosaic/framework/tools/_scripts/mosaic-orchestrator-matrix-publish diff --git a/packages/mosaic/framework/bin/mosaic-orchestrator-run b/packages/mosaic/framework/tools/_scripts/mosaic-orchestrator-run similarity index 100% rename from packages/mosaic/framework/bin/mosaic-orchestrator-run rename to packages/mosaic/framework/tools/_scripts/mosaic-orchestrator-run diff --git a/packages/mosaic/framework/bin/mosaic-orchestrator-sync-tasks b/packages/mosaic/framework/tools/_scripts/mosaic-orchestrator-sync-tasks similarity index 100% rename from packages/mosaic/framework/bin/mosaic-orchestrator-sync-tasks rename to packages/mosaic/framework/tools/_scripts/mosaic-orchestrator-sync-tasks diff --git a/packages/mosaic/framework/bin/mosaic-projects b/packages/mosaic/framework/tools/_scripts/mosaic-projects similarity index 97% rename from packages/mosaic/framework/bin/mosaic-projects rename to packages/mosaic/framework/tools/_scripts/mosaic-projects index 1c4f12c..4a3e0b5 100755 --- a/packages/mosaic/framework/bin/mosaic-projects +++ b/packages/mosaic/framework/tools/_scripts/mosaic-projects @@ -151,7 +151,7 @@ case "$cmd" in [[ -n "$quality_template" ]] && args+=(--quality-template "$quality_template") args+=("$repo") echo "[mosaic-projects] bootstrap: $repo" - "$MOSAIC_HOME/bin/mosaic-bootstrap-repo" "${args[@]}" + "$MOSAIC_HOME/tools/_scripts/mosaic-bootstrap-repo" "${args[@]}" add_repo "$repo" || true done ;; @@ -193,7 +193,7 @@ case "$cmd" in drain) args=(--poll-sec "$poll_sec") [[ $no_sync -eq 1 ]] && args+=(--no-sync) - "$MOSAIC_HOME/bin/mosaic-orchestrator-drain" "${args[@]}" + "$MOSAIC_HOME/tools/_scripts/mosaic-orchestrator-drain" "${args[@]}" ;; status) echo "[mosaic-projects] no daemon script in repo; run from bootstrapped repo or re-bootstrap" diff --git a/packages/mosaic/framework/bin/mosaic-prune-legacy-runtime b/packages/mosaic/framework/tools/_scripts/mosaic-prune-legacy-runtime similarity index 100% rename from packages/mosaic/framework/bin/mosaic-prune-legacy-runtime rename to packages/mosaic/framework/tools/_scripts/mosaic-prune-legacy-runtime diff --git a/packages/mosaic/framework/bin/mosaic-quality-apply b/packages/mosaic/framework/tools/_scripts/mosaic-quality-apply similarity index 100% rename from packages/mosaic/framework/bin/mosaic-quality-apply rename to packages/mosaic/framework/tools/_scripts/mosaic-quality-apply diff --git a/packages/mosaic/framework/bin/mosaic-quality-verify b/packages/mosaic/framework/tools/_scripts/mosaic-quality-verify similarity index 100% rename from packages/mosaic/framework/bin/mosaic-quality-verify rename to packages/mosaic/framework/tools/_scripts/mosaic-quality-verify diff --git a/packages/mosaic/framework/bin/mosaic-release-upgrade b/packages/mosaic/framework/tools/_scripts/mosaic-release-upgrade similarity index 100% rename from packages/mosaic/framework/bin/mosaic-release-upgrade rename to packages/mosaic/framework/tools/_scripts/mosaic-release-upgrade diff --git a/packages/mosaic/framework/bin/mosaic-release-upgrade.ps1 b/packages/mosaic/framework/tools/_scripts/mosaic-release-upgrade.ps1 similarity index 100% rename from packages/mosaic/framework/bin/mosaic-release-upgrade.ps1 rename to packages/mosaic/framework/tools/_scripts/mosaic-release-upgrade.ps1 diff --git a/packages/mosaic/framework/bin/mosaic-session-end b/packages/mosaic/framework/tools/_scripts/mosaic-session-end similarity index 100% rename from packages/mosaic/framework/bin/mosaic-session-end rename to packages/mosaic/framework/tools/_scripts/mosaic-session-end diff --git a/packages/mosaic/framework/bin/mosaic-session-start b/packages/mosaic/framework/tools/_scripts/mosaic-session-start similarity index 100% rename from packages/mosaic/framework/bin/mosaic-session-start rename to packages/mosaic/framework/tools/_scripts/mosaic-session-start diff --git a/packages/mosaic/framework/bin/mosaic-sync-skills b/packages/mosaic/framework/tools/_scripts/mosaic-sync-skills similarity index 100% rename from packages/mosaic/framework/bin/mosaic-sync-skills rename to packages/mosaic/framework/tools/_scripts/mosaic-sync-skills diff --git a/packages/mosaic/framework/bin/mosaic-sync-skills.ps1 b/packages/mosaic/framework/tools/_scripts/mosaic-sync-skills.ps1 similarity index 100% rename from packages/mosaic/framework/bin/mosaic-sync-skills.ps1 rename to packages/mosaic/framework/tools/_scripts/mosaic-sync-skills.ps1 diff --git a/packages/mosaic/framework/bin/mosaic-upgrade b/packages/mosaic/framework/tools/_scripts/mosaic-upgrade similarity index 100% rename from packages/mosaic/framework/bin/mosaic-upgrade rename to packages/mosaic/framework/tools/_scripts/mosaic-upgrade diff --git a/packages/mosaic/framework/bin/mosaic-upgrade-slaves b/packages/mosaic/framework/tools/_scripts/mosaic-upgrade-slaves similarity index 97% rename from packages/mosaic/framework/bin/mosaic-upgrade-slaves rename to packages/mosaic/framework/tools/_scripts/mosaic-upgrade-slaves index 6b18f8b..2bc31e1 100755 --- a/packages/mosaic/framework/bin/mosaic-upgrade-slaves +++ b/packages/mosaic/framework/tools/_scripts/mosaic-upgrade-slaves @@ -2,7 +2,7 @@ set -euo pipefail MOSAIC_HOME="${MOSAIC_HOME:-$HOME/.config/mosaic}" -BOOTSTRAP_CMD="$MOSAIC_HOME/bin/mosaic-bootstrap-repo" +BOOTSTRAP_CMD="$MOSAIC_HOME/tools/_scripts/mosaic-bootstrap-repo" roots=("$HOME/src") apply=0 diff --git a/packages/mosaic/framework/bin/mosaic-wizard b/packages/mosaic/framework/tools/_scripts/mosaic-wizard similarity index 100% rename from packages/mosaic/framework/bin/mosaic-wizard rename to packages/mosaic/framework/tools/_scripts/mosaic-wizard diff --git a/packages/mosaic/framework/tools/orchestrator/session-run.sh b/packages/mosaic/framework/tools/orchestrator/session-run.sh index dcbda36..cdcb245 100755 --- a/packages/mosaic/framework/tools/orchestrator/session-run.sh +++ b/packages/mosaic/framework/tools/orchestrator/session-run.sh @@ -80,11 +80,11 @@ echo -e "${C_CYAN}Capsule:${C_RESET} $(next_task_capsule_path "$PROJECT")" cd "$PROJECT" if [[ "$YOLO" == true ]]; then - exec "$MOSAIC_HOME/bin/mosaic" yolo "$runtime" "$launch_prompt" + exec mosaic yolo "$runtime" "$launch_prompt" elif [[ "$runtime" == "claude" ]]; then - exec "$MOSAIC_HOME/bin/mosaic" claude "$launch_prompt" + exec mosaic claude "$launch_prompt" elif [[ "$runtime" == "codex" ]]; then - exec "$MOSAIC_HOME/bin/mosaic" codex "$launch_prompt" + exec mosaic codex "$launch_prompt" fi echo -e "${C_RED}Unsupported coord runtime: $runtime${C_RESET}" >&2 diff --git a/scripts/agent/orchestrator-daemon.sh b/scripts/agent/orchestrator-daemon.sh index 73c29ba..eea1056 100755 --- a/scripts/agent/orchestrator-daemon.sh +++ b/scripts/agent/orchestrator-daemon.sh @@ -69,12 +69,12 @@ case "$cmd" in echo "[agent-framework] orchestrator already running (pid=$(cat "$PID_FILE"))" exit 0 fi - nohup "$MOSAIC_HOME/bin/mosaic-orchestrator-drain" --poll-sec "$poll_sec" $sync_arg >"$LOG_FILE" 2>&1 & + nohup "$MOSAIC_HOME/tools/_scripts/mosaic-orchestrator-drain" --poll-sec "$poll_sec" $sync_arg >"$LOG_FILE" 2>&1 & echo "$!" > "$PID_FILE" echo "[agent-framework] orchestrator started (pid=$!, log=$LOG_FILE)" ;; drain) - exec "$MOSAIC_HOME/bin/mosaic-orchestrator-drain" --poll-sec "$poll_sec" $sync_arg + exec "$MOSAIC_HOME/tools/_scripts/mosaic-orchestrator-drain" --poll-sec "$poll_sec" $sync_arg ;; stop) if ! is_running; then diff --git a/tools/install.sh b/tools/install.sh index 4a4cffd..743f059 100755 --- a/tools/install.sh +++ b/tools/install.sh @@ -114,10 +114,10 @@ version_lt() { } framework_version() { - # Read VERSION from the installed mosaic launcher - local mosaic_bin="$MOSAIC_HOME/bin/mosaic" - if [[ -f "$mosaic_bin" ]]; then - grep -m1 '^VERSION=' "$mosaic_bin" 2>/dev/null | cut -d'"' -f2 || true + # Read framework schema version stamp + local vf="$MOSAIC_HOME/.framework-version" + if [[ -f "$vf" ]]; then + cat "$vf" 2>/dev/null || true fi } @@ -145,7 +145,7 @@ if [[ "$FLAG_FRAMEWORK" == "true" ]]; then FRAMEWORK_CURRENT="$(framework_version)" HAS_FRAMEWORK=false - [[ -d "$MOSAIC_HOME/bin" ]] && [[ -f "$MOSAIC_HOME/bin/mosaic" ]] && HAS_FRAMEWORK=true + [[ -f "$MOSAIC_HOME/AGENTS.md" ]] || [[ -f "$MOSAIC_HOME/.framework-version" ]] && HAS_FRAMEWORK=true if [[ -n "$FRAMEWORK_CURRENT" ]]; then dim " Installed: framework v${FRAMEWORK_CURRENT}" @@ -202,13 +202,8 @@ if [[ "$FLAG_FRAMEWORK" == "true" ]]; then ok "Framework installed" echo "" - # Ensure framework bin is on PATH - FRAMEWORK_BIN="$MOSAIC_HOME/bin" - if [[ ":$PATH:" != *":$FRAMEWORK_BIN:"* ]]; then - warn "$FRAMEWORK_BIN is not on your PATH" - dim " The 'mosaic' launcher lives here. Add to your shell rc:" - dim " export PATH=\"$FRAMEWORK_BIN:\$PATH\"" - fi + # Framework bin is no longer needed on PATH — the npm CLI delegates + # to mosaic-launch directly via its absolute path. fi fi @@ -290,7 +285,6 @@ if [[ "$FLAG_CLI" == "true" ]]; then # PATH check for npm prefix if [[ ":$PATH:" != *":$PREFIX/bin:"* ]]; then warn "$PREFIX/bin is not on your PATH" - dim " The 'mosaic' TUI/gateway CLI lives here (separate from the launcher)." dim " Add to your shell rc: export PATH=\"$PREFIX/bin:\$PATH\"" fi fi @@ -303,28 +297,9 @@ fi if [[ "$FLAG_CHECK" == "false" ]]; then step "Summary" - echo " ${BOLD}Framework launcher:${RESET} $MOSAIC_HOME/bin/mosaic" - echo " ${DIM}mosaic claude, mosaic yolo claude, mosaic pi, mosaic doctor, …${RESET}" + echo " ${BOLD}mosaic:${RESET} $PREFIX/bin/mosaic" + dim " Framework data: $MOSAIC_HOME/" echo "" - echo " ${BOLD}npm CLI (TUI):${RESET} $PREFIX/bin/mosaic" - echo " ${DIM}mosaic tui, mosaic login, mosaic wizard, mosaic update, …${RESET}" - echo "" - - # Warn if there's a naming collision (both on PATH) - FRAMEWORK_BIN="$MOSAIC_HOME/bin" - if [[ ":$PATH:" == *":$FRAMEWORK_BIN:"* ]] && [[ ":$PATH:" == *":$PREFIX/bin:"* ]]; then - # Check which one wins - WHICH_MOSAIC="$(command -v mosaic 2>/dev/null || true)" - if [[ -n "$WHICH_MOSAIC" ]]; then - dim " Active 'mosaic' binary: $WHICH_MOSAIC" - if [[ "$WHICH_MOSAIC" == "$FRAMEWORK_BIN/mosaic" ]]; then - dim " (Framework launcher takes priority — this is correct)" - else - warn "npm CLI shadows the framework launcher!" - dim " Ensure $FRAMEWORK_BIN appears BEFORE $PREFIX/bin in your PATH." - fi - fi - fi # First install guidance if [[ ! -f "$MOSAIC_HOME/SOUL.md" ]]; then