diff --git a/eslint.config.mjs b/eslint.config.mjs index 899221d..8c88d1c 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -10,6 +10,7 @@ export default tseslint.config( '**/.next/**', '**/coverage/**', '**/drizzle.config.ts', + '**/framework/**', ], }, { diff --git a/packages/mosaic/framework/bin/mosaic b/packages/mosaic/framework/bin/mosaic new file mode 100755 index 0000000..4cb9c55 --- /dev/null +++ b/packages/mosaic/framework/bin/mosaic @@ -0,0 +1,849 @@ +#!/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: curl -sL https://git.mosaicstack.dev/mosaic/bootstrap/raw/branch/main/remote-install.sh | 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: cd ~/src/mosaic-bootstrap && bash 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-bootstrap-repo b/packages/mosaic/framework/bin/mosaic-bootstrap-repo new file mode 100755 index 0000000..b24b0b8 --- /dev/null +++ b/packages/mosaic/framework/bin/mosaic-bootstrap-repo @@ -0,0 +1,126 @@ +#!/usr/bin/env bash +set -euo pipefail + +TARGET_DIR="$(pwd)" +FORCE=0 +QUALITY_TEMPLATE="" + +while [[ $# -gt 0 ]]; do + case "$1" in + --force) + FORCE=1 + shift + ;; + --quality-template) + QUALITY_TEMPLATE="${2:-}" + shift 2 + ;; + *) + TARGET_DIR="$1" + shift + ;; + esac +done + +if [[ ! -d "$TARGET_DIR" ]]; then + echo "[mosaic] Target directory does not exist: $TARGET_DIR" >&2 + exit 1 +fi + +MOSAIC_HOME="${MOSAIC_HOME:-$HOME/.config/mosaic}" +TEMPLATE_ROOT="$MOSAIC_HOME/templates/repo" + +if [[ ! -d "$TEMPLATE_ROOT" ]]; then + echo "[mosaic] Missing templates at $TEMPLATE_ROOT" >&2 + echo "[mosaic] Install or refresh framework: ~/.config/mosaic/install.sh" >&2 + exit 1 +fi + +mkdir -p "$TARGET_DIR/.mosaic" "$TARGET_DIR/scripts/agent" +mkdir -p "$TARGET_DIR/.mosaic/orchestrator" "$TARGET_DIR/.mosaic/orchestrator/logs" "$TARGET_DIR/.mosaic/orchestrator/results" + +copy_file() { + local src="$1" + local dst="$2" + + if [[ -f "$dst" && "$FORCE" -ne 1 ]]; then + echo "[mosaic] Skip existing: $dst" + return + fi + + cp "$src" "$dst" + echo "[mosaic] Wrote: $dst" +} + +copy_file "$TEMPLATE_ROOT/.mosaic/README.md" "$TARGET_DIR/.mosaic/README.md" +copy_file "$TEMPLATE_ROOT/.mosaic/repo-hooks.sh" "$TARGET_DIR/.mosaic/repo-hooks.sh" +copy_file "$TEMPLATE_ROOT/.mosaic/quality-rails.yml" "$TARGET_DIR/.mosaic/quality-rails.yml" +copy_file "$TEMPLATE_ROOT/.mosaic/orchestrator/config.json" "$TARGET_DIR/.mosaic/orchestrator/config.json" +copy_file "$TEMPLATE_ROOT/.mosaic/orchestrator/tasks.json" "$TARGET_DIR/.mosaic/orchestrator/tasks.json" +copy_file "$TEMPLATE_ROOT/.mosaic/orchestrator/state.json" "$TARGET_DIR/.mosaic/orchestrator/state.json" +copy_file "$TEMPLATE_ROOT/.mosaic/orchestrator/matrix_state.json" "$TARGET_DIR/.mosaic/orchestrator/matrix_state.json" +copy_file "$TEMPLATE_ROOT/.mosaic/orchestrator/logs/.gitkeep" "$TARGET_DIR/.mosaic/orchestrator/logs/.gitkeep" +copy_file "$TEMPLATE_ROOT/.mosaic/orchestrator/results/.gitkeep" "$TARGET_DIR/.mosaic/orchestrator/results/.gitkeep" + +for file in "$TEMPLATE_ROOT"/scripts/agent/*.sh; do + base="$(basename "$file")" + copy_file "$file" "$TARGET_DIR/scripts/agent/$base" + chmod +x "$TARGET_DIR/scripts/agent/$base" +done + +if [[ ! -f "$TARGET_DIR/AGENTS.md" ]]; then + cat > "$TARGET_DIR/AGENTS.md" <<'AGENTS_EOF' +# Agent Guidelines + +## Required Load Order + +1. `~/.config/mosaic/SOUL.md` +2. `~/.config/mosaic/STANDARDS.md` +3. `~/.config/mosaic/AGENTS.md` +4. `~/.config/mosaic/guides/E2E-DELIVERY.md` +5. `AGENTS.md` (this file) +6. Runtime-specific guide: `~/.config/mosaic/runtime//RUNTIME.md` +7. `.mosaic/repo-hooks.sh` + +## Session Lifecycle + +```bash +bash scripts/agent/session-start.sh +bash scripts/agent/critical.sh +bash scripts/agent/session-end.sh +``` + +## Shared Tools + +- Quality and orchestration guides: `~/.config/mosaic/guides/` +- Shared automation tools: `~/.config/mosaic/tools/` + +## Repo-Specific Notes + +- Add project constraints and workflows here. +- Implement hook functions in `.mosaic/repo-hooks.sh`. +- Scratchpads are mandatory for non-trivial tasks. +AGENTS_EOF + echo "[mosaic] Wrote: $TARGET_DIR/AGENTS.md" +else + echo "[mosaic] AGENTS.md exists; add standards load order if missing" +fi + +echo "[mosaic] Repo bootstrap complete: $TARGET_DIR" +echo "[mosaic] Next: edit $TARGET_DIR/.mosaic/repo-hooks.sh with project workflows" +echo "[mosaic] Optional: apply quality tools via ~/.config/mosaic/bin/mosaic-quality-apply --template