feat: complete framework migration — PowerShell, adapters, guides, profiles, tests
Some checks failed
ci/woodpecker/push/ci Pipeline failed
ci/woodpecker/pr/ci Pipeline failed

Completes the bootstrap repo migration with remaining files:
- PowerShell scripts (.ps1) for Windows support (bin/ + tools/)
- Runtime adapters (claude, codex, generic, pi)
- Guides (17 .md files) and profiles (domains, tech-stacks, workflows)
- Wizard test suite (6 test files from bootstrap tests/)
- Memory placeholder, audit history

Bootstrap repo (mosaic/bootstrap) is now fully superseded:
- All 335 files accounted for
- 5 build config files (package.json, tsconfig, etc.) not needed —
  monorepo has its own at packages/mosaic/
- skills-local/ superseded by monorepo skills/ with mosaic-* naming
- src/ already lives at packages/mosaic/src/
This commit is contained in:
Jason Woltje
2026-04-01 21:23:26 -05:00
parent 53199122d8
commit 6e6ee37da0
51 changed files with 9410 additions and 0 deletions

View File

@@ -0,0 +1,283 @@
# mosaic-doctor.ps1
# Audits Mosaic runtime state and detects drift across agent runtimes.
# PowerShell equivalent of mosaic-doctor (bash).
$ErrorActionPreference = "Stop"
param(
[switch]$FailOnWarn,
[switch]$Verbose,
[switch]$Help
)
$MosaicHome = if ($env:MOSAIC_HOME) { $env:MOSAIC_HOME } else { Join-Path $env:USERPROFILE ".config\mosaic" }
if ($Help) {
Write-Host @"
Usage: mosaic-doctor.ps1 [-FailOnWarn] [-Verbose] [-Help]
Audit Mosaic runtime state and detect drift across agent runtimes.
"@
exit 0
}
$script:warnCount = 0
function Warn {
param([string]$Message)
$script:warnCount++
Write-Host "[WARN] $Message" -ForegroundColor Yellow
}
function Pass {
param([string]$Message)
if ($Verbose) { Write-Host "[OK] $Message" -ForegroundColor Green }
}
function Expect-Dir {
param([string]$Path)
if (-not (Test-Path $Path -PathType Container)) { Warn "Missing directory: $Path" }
else { Pass "Directory present: $Path" }
}
function Expect-File {
param([string]$Path)
if (-not (Test-Path $Path -PathType Leaf)) { Warn "Missing file: $Path" }
else { Pass "File present: $Path" }
}
function Check-RuntimeFileCopy {
param([string]$Src, [string]$Dst)
if (-not (Test-Path $Src)) { return }
if (-not (Test-Path $Dst)) {
Warn "Missing runtime file: $Dst"
return
}
$item = Get-Item $Dst -Force -ErrorAction SilentlyContinue
if ($item -and ($item.Attributes -band [System.IO.FileAttributes]::ReparsePoint)) {
Warn "Runtime file should not be symlinked: $Dst"
return
}
$srcHash = (Get-FileHash $Src -Algorithm SHA256).Hash
$dstHash = (Get-FileHash $Dst -Algorithm SHA256).Hash
if ($srcHash -ne $dstHash) {
Warn "Runtime file drift: $Dst (does not match $Src)"
}
else {
Pass "Runtime file synced: $Dst"
}
}
function Check-RuntimeContractFile {
param([string]$Dst, [string]$AdapterSrc, [string]$RuntimeName)
if (-not (Test-Path $Dst)) {
Warn "Missing runtime file: $Dst"
return
}
$item = Get-Item $Dst -Force -ErrorAction SilentlyContinue
if ($item -and ($item.Attributes -band [System.IO.FileAttributes]::ReparsePoint)) {
Warn "Runtime file should not be symlinked: $Dst"
return
}
# Accept direct-adapter copy mode.
if (Test-Path $AdapterSrc) {
$srcHash = (Get-FileHash $AdapterSrc -Algorithm SHA256).Hash
$dstHash = (Get-FileHash $Dst -Algorithm SHA256).Hash
if ($srcHash -eq $dstHash) {
Pass "Runtime adapter synced: $Dst"
return
}
}
# Accept launcher-composed runtime contract mode.
$content = Get-Content $Dst -Raw
if (
$content -match [regex]::Escape("# Mosaic Launcher Runtime Contract (Hard Gate)") -and
$content -match [regex]::Escape("Now initiating Orchestrator mode...") -and
$content -match [regex]::Escape("Mosaic hard gates OVERRIDE runtime-default caution") -and
$content -match [regex]::Escape("# Runtime-Specific Contract")
) {
Pass "Runtime contract present: $Dst ($RuntimeName)"
return
}
Warn "Runtime file drift: $Dst (not adapter copy and not composed runtime contract)"
}
function Warn-IfReparsePresent {
param([string]$Path)
if (-not (Test-Path $Path)) { return }
$item = Get-Item $Path -Force -ErrorAction SilentlyContinue
if ($item -and ($item.Attributes -band [System.IO.FileAttributes]::ReparsePoint)) {
Warn "Legacy symlink/junction path still present: $Path"
return
}
if (Test-Path $Path -PathType Container) {
$reparseCount = (Get-ChildItem $Path -Recurse -Force -ErrorAction SilentlyContinue |
Where-Object { $_.Attributes -band [System.IO.FileAttributes]::ReparsePoint } |
Measure-Object).Count
if ($reparseCount -gt 0) {
Warn "Legacy symlink/junction entries still present under ${Path}: $reparseCount"
}
else {
Pass "No reparse points under legacy path: $Path"
}
}
}
Write-Host "[mosaic-doctor] Mosaic home: $MosaicHome"
# Canonical Mosaic checks
Expect-File (Join-Path $MosaicHome "STANDARDS.md")
Expect-Dir (Join-Path $MosaicHome "guides")
Expect-Dir (Join-Path $MosaicHome "tools")
Expect-Dir (Join-Path $MosaicHome "tools\quality")
Expect-Dir (Join-Path $MosaicHome "tools\orchestrator-matrix")
Expect-Dir (Join-Path $MosaicHome "profiles")
Expect-Dir (Join-Path $MosaicHome "templates\agent")
Expect-Dir (Join-Path $MosaicHome "skills")
Expect-Dir (Join-Path $MosaicHome "skills-local")
Expect-File (Join-Path $MosaicHome "bin\mosaic-link-runtime-assets")
Expect-File (Join-Path $MosaicHome "bin\mosaic-ensure-sequential-thinking.ps1")
Expect-File (Join-Path $MosaicHome "bin\mosaic-sync-skills")
Expect-File (Join-Path $MosaicHome "bin\mosaic-projects")
Expect-File (Join-Path $MosaicHome "bin\mosaic-quality-apply")
Expect-File (Join-Path $MosaicHome "bin\mosaic-quality-verify")
Expect-File (Join-Path $MosaicHome "bin\mosaic-orchestrator-run")
Expect-File (Join-Path $MosaicHome "bin\mosaic-orchestrator-sync-tasks")
Expect-File (Join-Path $MosaicHome "bin\mosaic-orchestrator-drain")
Expect-File (Join-Path $MosaicHome "bin\mosaic-orchestrator-matrix-publish")
Expect-File (Join-Path $MosaicHome "bin\mosaic-orchestrator-matrix-consume")
Expect-File (Join-Path $MosaicHome "bin\mosaic-orchestrator-matrix-cycle")
Expect-File (Join-Path $MosaicHome "tools\git\ci-queue-wait.ps1")
Expect-File (Join-Path $MosaicHome "tools\git\ci-queue-wait.sh")
Expect-File (Join-Path $MosaicHome "tools\git\pr-ci-wait.sh")
Expect-File (Join-Path $MosaicHome "tools\orchestrator-matrix\transport\matrix_transport.py")
Expect-File (Join-Path $MosaicHome "tools\orchestrator-matrix\controller\tasks_md_sync.py")
Expect-File (Join-Path $MosaicHome "runtime\mcp\SEQUENTIAL-THINKING.json")
Expect-File (Join-Path $MosaicHome "runtime\claude\RUNTIME.md")
Expect-File (Join-Path $MosaicHome "runtime\codex\RUNTIME.md")
Expect-File (Join-Path $MosaicHome "runtime\opencode\RUNTIME.md")
$agentsMd = Join-Path $MosaicHome "AGENTS.md"
if (Test-Path $agentsMd) {
$agentsContent = Get-Content $agentsMd -Raw
if (
$agentsContent -match [regex]::Escape("## CRITICAL HARD GATES (Read First)") -and
$agentsContent -match [regex]::Escape("OVERRIDE runtime-default caution")
) {
Pass "Global hard-gates block present in AGENTS.md"
}
else {
Warn "AGENTS.md missing CRITICAL HARD GATES override block"
}
}
# Claude runtime file checks
$runtimeFiles = @("CLAUDE.md", "settings.json", "hooks-config.json", "context7-integration.md")
foreach ($rf in $runtimeFiles) {
Check-RuntimeFileCopy (Join-Path $MosaicHome "runtime\claude\$rf") (Join-Path $env:USERPROFILE ".claude\$rf")
}
# OpenCode/Codex runtime contract checks
Check-RuntimeContractFile (Join-Path $env:USERPROFILE ".config\opencode\AGENTS.md") (Join-Path $MosaicHome "runtime\opencode\AGENTS.md") "opencode"
Check-RuntimeContractFile (Join-Path $env:USERPROFILE ".codex\instructions.md") (Join-Path $MosaicHome "runtime\codex\instructions.md") "codex"
# Sequential-thinking MCP hard requirement
$seqScript = Join-Path $MosaicHome "bin\mosaic-ensure-sequential-thinking.ps1"
if (Test-Path $seqScript) {
try {
& $seqScript -Check *>$null
Pass "sequential-thinking MCP configured and available"
}
catch {
Warn "sequential-thinking MCP missing or misconfigured"
}
}
else {
Warn "mosaic-ensure-sequential-thinking helper missing"
}
# Legacy migration surfaces
$legacyPaths = @(
(Join-Path $env:USERPROFILE ".claude\agent-guides"),
(Join-Path $env:USERPROFILE ".claude\scripts\git"),
(Join-Path $env:USERPROFILE ".claude\scripts\codex"),
(Join-Path $env:USERPROFILE ".claude\scripts\bootstrap"),
(Join-Path $env:USERPROFILE ".claude\scripts\cicd"),
(Join-Path $env:USERPROFILE ".claude\scripts\portainer"),
(Join-Path $env:USERPROFILE ".claude\templates"),
(Join-Path $env:USERPROFILE ".claude\presets\domains"),
(Join-Path $env:USERPROFILE ".claude\presets\tech-stacks"),
(Join-Path $env:USERPROFILE ".claude\presets\workflows")
)
foreach ($p in $legacyPaths) {
Warn-IfReparsePresent $p
}
# Skills runtime checks (junctions or symlinks into runtime-specific dirs)
$linkTargets = @(
(Join-Path $env:USERPROFILE ".claude\skills"),
(Join-Path $env:USERPROFILE ".codex\skills"),
(Join-Path $env:USERPROFILE ".config\opencode\skills")
)
$skillSources = @($MosaicHome + "\skills", $MosaicHome + "\skills-local")
foreach ($runtimeSkills in $linkTargets) {
if (-not (Test-Path $runtimeSkills)) { continue }
foreach ($sourceDir in $skillSources) {
if (-not (Test-Path $sourceDir)) { continue }
Get-ChildItem $sourceDir -Directory | Where-Object { -not $_.Name.StartsWith(".") } | ForEach-Object {
$name = $_.Name
$skillPath = $_.FullName
$target = Join-Path $runtimeSkills $name
if (-not (Test-Path $target)) {
Warn "Missing skill link: $target"
return
}
$item = Get-Item $target -Force -ErrorAction SilentlyContinue
if (-not ($item.Attributes -band [System.IO.FileAttributes]::ReparsePoint)) {
Warn "Non-junction skill entry: $target"
return
}
$targetResolved = $item.Target
if (-not $targetResolved -or (Resolve-Path $targetResolved -ErrorAction SilentlyContinue).Path -ne (Resolve-Path $skillPath -ErrorAction SilentlyContinue).Path) {
Warn "Drifted skill link: $target (expected -> $skillPath)"
}
else {
Pass "Linked skill: $target"
}
}
}
}
# Broken junctions/symlinks in managed runtime skill dirs
$brokenLinks = 0
foreach ($d in $linkTargets) {
if (-not (Test-Path $d)) { continue }
Get-ChildItem $d -Force -ErrorAction SilentlyContinue | Where-Object {
($_.Attributes -band [System.IO.FileAttributes]::ReparsePoint) -and -not (Test-Path $_.FullName)
} | ForEach-Object { $brokenLinks++ }
}
if ($brokenLinks -gt 0) {
Warn "Broken skill junctions/symlinks detected: $brokenLinks"
}
Write-Host "[mosaic-doctor] warnings=$($script:warnCount)"
if ($FailOnWarn -and $script:warnCount -gt 0) {
exit 1
}

View File

@@ -0,0 +1,114 @@
# mosaic-ensure-sequential-thinking.ps1
param(
[switch]$Check
)
$ErrorActionPreference = "Stop"
$Pkg = "@modelcontextprotocol/server-sequential-thinking"
function Require-Binary {
param([string]$Name)
if (-not (Get-Command $Name -ErrorAction SilentlyContinue)) {
throw "Required binary missing: $Name"
}
}
function Warm-Package {
$null = & npx -y $Pkg --help 2>$null
}
function Set-ClaudeConfig {
$path = Join-Path $env:USERPROFILE ".claude\settings.json"
New-Item -ItemType Directory -Path (Split-Path $path -Parent) -Force | Out-Null
$data = @{}
if (Test-Path $path) {
try { $data = Get-Content $path -Raw | ConvertFrom-Json -AsHashtable } catch { $data = @{} }
}
if (-not $data.ContainsKey("mcpServers") -or -not ($data["mcpServers"] -is [hashtable])) {
$data["mcpServers"] = @{}
}
$data["mcpServers"]["sequential-thinking"] = @{
command = "npx"
args = @("-y", "@modelcontextprotocol/server-sequential-thinking")
}
$data | ConvertTo-Json -Depth 20 | Set-Content -Path $path -Encoding UTF8
}
function Set-CodexConfig {
$path = Join-Path $env:USERPROFILE ".codex\config.toml"
New-Item -ItemType Directory -Path (Split-Path $path -Parent) -Force | Out-Null
if (-not (Test-Path $path)) { New-Item -ItemType File -Path $path -Force | Out-Null }
$content = Get-Content $path -Raw
$content = [regex]::Replace($content, "(?ms)^\[mcp_servers\.(sequential-thinking|sequential_thinking)\].*?(?=^\[|\z)", "")
$content = $content.TrimEnd() + "`n`n[mcp_servers.sequential-thinking]`ncommand = `"npx`"`nargs = [`"-y`", `"@modelcontextprotocol/server-sequential-thinking`"]`n"
Set-Content -Path $path -Value $content -Encoding UTF8
}
function Set-OpenCodeConfig {
$path = Join-Path $env:USERPROFILE ".config\opencode\config.json"
New-Item -ItemType Directory -Path (Split-Path $path -Parent) -Force | Out-Null
$data = @{}
if (Test-Path $path) {
try { $data = Get-Content $path -Raw | ConvertFrom-Json -AsHashtable } catch { $data = @{} }
}
if (-not $data.ContainsKey("mcp") -or -not ($data["mcp"] -is [hashtable])) {
$data["mcp"] = @{}
}
$data["mcp"]["sequential-thinking"] = @{
type = "local"
command = @("npx", "-y", "@modelcontextprotocol/server-sequential-thinking")
enabled = $true
}
$data | ConvertTo-Json -Depth 20 | Set-Content -Path $path -Encoding UTF8
}
function Test-Configs {
$claudeOk = $false
$codexOk = $false
$opencodeOk = $false
$claudePath = Join-Path $env:USERPROFILE ".claude\settings.json"
if (Test-Path $claudePath) {
try {
$c = Get-Content $claudePath -Raw | ConvertFrom-Json -AsHashtable
$claudeOk = $c.ContainsKey("mcpServers") -and $c["mcpServers"].ContainsKey("sequential-thinking")
} catch {}
}
$codexPath = Join-Path $env:USERPROFILE ".codex\config.toml"
if (Test-Path $codexPath) {
$raw = Get-Content $codexPath -Raw
$codexOk = $raw -match "\[mcp_servers\.(sequential-thinking|sequential_thinking)\]" -and $raw -match "@modelcontextprotocol/server-sequential-thinking"
}
$opencodePath = Join-Path $env:USERPROFILE ".config\opencode\config.json"
if (Test-Path $opencodePath) {
try {
$o = Get-Content $opencodePath -Raw | ConvertFrom-Json -AsHashtable
$opencodeOk = $o.ContainsKey("mcp") -and $o["mcp"].ContainsKey("sequential-thinking")
} catch {}
}
if (-not ($claudeOk -and $codexOk -and $opencodeOk)) {
throw "Sequential-thinking MCP runtime config is incomplete"
}
}
Require-Binary node
Require-Binary npx
Warm-Package
if ($Check) {
Test-Configs
Write-Host "[mosaic-seq] sequential-thinking MCP is configured and available"
exit 0
}
Set-ClaudeConfig
Set-CodexConfig
Set-OpenCodeConfig
Write-Host "[mosaic-seq] sequential-thinking MCP configured for Claude, Codex, and OpenCode"

View File

@@ -0,0 +1,144 @@
# mosaic-init.ps1 — Interactive SOUL.md generator (Windows)
#
# Usage:
# mosaic-init.ps1 # Interactive mode
# mosaic-init.ps1 -Name "Jarvis" -Style direct # Flag overrides
$ErrorActionPreference = "Stop"
param(
[string]$Name,
[string]$Role,
[ValidateSet("direct", "friendly", "formal")]
[string]$Style,
[string]$Accessibility,
[string]$Guardrails,
[switch]$NonInteractive,
[switch]$Help
)
$MosaicHome = if ($env:MOSAIC_HOME) { $env:MOSAIC_HOME } else { Join-Path $env:USERPROFILE ".config\mosaic" }
$Template = Join-Path $MosaicHome "templates\SOUL.md.template"
$Output = Join-Path $MosaicHome "SOUL.md"
if ($Help) {
Write-Host @"
Usage: mosaic-init.ps1 [-Name <name>] [-Role <desc>] [-Style direct|friendly|formal]
[-Accessibility <prefs>] [-Guardrails <rules>] [-NonInteractive]
Generate ~/.config/mosaic/SOUL.md - the universal agent identity contract.
Interactive by default. Use flags to skip prompts.
"@
exit 0
}
function Prompt-IfEmpty {
param([string]$Current, [string]$PromptText, [string]$Default = "")
if ($Current) { return $Current }
if ($NonInteractive) {
if ($Default) { return $Default }
Write-Host "[mosaic-init] ERROR: Value required in non-interactive mode: $PromptText" -ForegroundColor Red
exit 1
}
$display = if ($Default) { "$PromptText [$Default]" } else { $PromptText }
$value = Read-Host $display
if (-not $value -and $Default) { return $Default }
return $value
}
Write-Host "[mosaic-init] Generating SOUL.md - your universal agent identity contract"
Write-Host ""
$Name = Prompt-IfEmpty $Name "What name should agents use" "Assistant"
$Role = Prompt-IfEmpty $Role "Agent role description" "execution partner and visibility engine"
if (-not $Style) {
if ($NonInteractive) {
$Style = "direct"
}
else {
Write-Host ""
Write-Host "Communication style:"
Write-Host " 1) direct - Concise, no fluff, actionable"
Write-Host " 2) friendly - Warm but efficient, conversational"
Write-Host " 3) formal - Professional, structured, thorough"
$choice = Read-Host "Choose [1/2/3] (default: 1)"
$Style = switch ($choice) {
"2" { "friendly" }
"3" { "formal" }
default { "direct" }
}
}
}
$Accessibility = Prompt-IfEmpty $Accessibility "Accessibility preferences (or 'none')" "none"
if (-not $Guardrails -and -not $NonInteractive) {
Write-Host ""
$Guardrails = Read-Host "Custom guardrails (optional, press Enter to skip)"
}
# Build behavioral principles
$BehavioralPrinciples = switch ($Style) {
"direct" {
"1. Clarity over performance theater.`n2. Practical execution over abstract planning.`n3. Truthfulness over confidence: state uncertainty explicitly.`n4. Visible state over hidden assumptions."
}
"friendly" {
"1. Be helpful and approachable while staying efficient.`n2. Provide context and explain reasoning when helpful.`n3. Truthfulness over confidence: state uncertainty explicitly.`n4. Visible state over hidden assumptions."
}
"formal" {
"1. Maintain professional, structured communication.`n2. Provide thorough analysis with explicit tradeoffs.`n3. Truthfulness over confidence: state uncertainty explicitly.`n4. Document decisions and rationale clearly."
}
}
if ($Accessibility -and $Accessibility -ne "none") {
$BehavioralPrinciples += "`n5. $Accessibility."
}
# Build communication style
$CommunicationStyle = switch ($Style) {
"direct" {
"- Be direct, concise, and concrete.`n- Avoid fluff, hype, and anthropomorphic roleplay.`n- Do not simulate certainty when facts are missing.`n- Prefer actionable next steps and explicit tradeoffs."
}
"friendly" {
"- Be warm and conversational while staying focused.`n- Explain your reasoning when it helps the user.`n- Do not simulate certainty when facts are missing.`n- Prefer actionable next steps with clear context."
}
"formal" {
"- Use professional, structured language.`n- Provide thorough explanations with supporting detail.`n- Do not simulate certainty when facts are missing.`n- Present options with explicit tradeoffs and recommendations."
}
}
# Format custom guardrails
$FormattedGuardrails = if ($Guardrails) { "- $Guardrails" } else { "" }
# Verify template
if (-not (Test-Path $Template)) {
Write-Host "[mosaic-init] ERROR: Template not found: $Template" -ForegroundColor Red
exit 1
}
# Generate SOUL.md
$content = Get-Content $Template -Raw
$content = $content -replace '\{\{AGENT_NAME\}\}', $Name
$content = $content -replace '\{\{ROLE_DESCRIPTION\}\}', $Role
$content = $content -replace '\{\{BEHAVIORAL_PRINCIPLES\}\}', $BehavioralPrinciples
$content = $content -replace '\{\{COMMUNICATION_STYLE\}\}', $CommunicationStyle
$content = $content -replace '\{\{CUSTOM_GUARDRAILS\}\}', $FormattedGuardrails
Set-Content -Path $Output -Value $content -Encoding UTF8
Write-Host ""
Write-Host "[mosaic-init] Generated: $Output"
Write-Host "[mosaic-init] Agent name: $Name"
Write-Host "[mosaic-init] Style: $Style"
# Push to runtime adapters
$linkScript = Join-Path $MosaicHome "bin\mosaic-link-runtime-assets.ps1"
if (Test-Path $linkScript) {
Write-Host "[mosaic-init] Updating runtime adapters..."
& $linkScript
}
Write-Host "[mosaic-init] Done. Launch with: mosaic claude"

View File

@@ -0,0 +1,111 @@
# mosaic-link-runtime-assets.ps1
# Syncs Mosaic runtime config files into agent runtime directories.
# PowerShell equivalent of mosaic-link-runtime-assets (bash).
$ErrorActionPreference = "Stop"
$MosaicHome = if ($env:MOSAIC_HOME) { $env:MOSAIC_HOME } else { Join-Path $env:USERPROFILE ".config\mosaic" }
$BackupStamp = Get-Date -Format "yyyyMMddHHmmss"
function Copy-FileManaged {
param([string]$Src, [string]$Dst)
$parent = Split-Path $Dst -Parent
if (-not (Test-Path $parent)) { New-Item -ItemType Directory -Path $parent -Force | Out-Null }
# Remove existing symlink/junction
$item = Get-Item $Dst -Force -ErrorAction SilentlyContinue
if ($item -and $item.Attributes -band [System.IO.FileAttributes]::ReparsePoint) {
Remove-Item $Dst -Force
}
if (Test-Path $Dst) {
$srcHash = (Get-FileHash $Src -Algorithm SHA256).Hash
$dstHash = (Get-FileHash $Dst -Algorithm SHA256).Hash
if ($srcHash -eq $dstHash) { return }
Rename-Item $Dst "$Dst.mosaic-bak-$BackupStamp"
}
Copy-Item $Src $Dst -Force
}
function Remove-LegacyPath {
param([string]$Path)
if (-not (Test-Path $Path)) { return }
$item = Get-Item $Path -Force -ErrorAction SilentlyContinue
if ($item -and $item.Attributes -band [System.IO.FileAttributes]::ReparsePoint) {
Remove-Item $Path -Force
return
}
if (Test-Path $Path -PathType Container) {
# Remove symlinks/junctions inside, then empty dirs
Get-ChildItem $Path -Recurse -Force | Where-Object {
$_.Attributes -band [System.IO.FileAttributes]::ReparsePoint
} | Remove-Item -Force
Get-ChildItem $Path -Recurse -Directory -Force |
Sort-Object { $_.FullName.Length } -Descending |
Where-Object { (Get-ChildItem $_.FullName -Force | Measure-Object).Count -eq 0 } |
Remove-Item -Force
}
}
# Remove legacy compatibility paths
$legacyPaths = @(
(Join-Path $env:USERPROFILE ".claude\agent-guides"),
(Join-Path $env:USERPROFILE ".claude\scripts\git"),
(Join-Path $env:USERPROFILE ".claude\scripts\codex"),
(Join-Path $env:USERPROFILE ".claude\scripts\bootstrap"),
(Join-Path $env:USERPROFILE ".claude\scripts\cicd"),
(Join-Path $env:USERPROFILE ".claude\scripts\portainer"),
(Join-Path $env:USERPROFILE ".claude\scripts\debug-hook.sh"),
(Join-Path $env:USERPROFILE ".claude\scripts\qa-hook-handler.sh"),
(Join-Path $env:USERPROFILE ".claude\scripts\qa-hook-stdin.sh"),
(Join-Path $env:USERPROFILE ".claude\scripts\qa-hook-wrapper.sh"),
(Join-Path $env:USERPROFILE ".claude\scripts\qa-queue-monitor.sh"),
(Join-Path $env:USERPROFILE ".claude\scripts\remediation-hook-handler.sh"),
(Join-Path $env:USERPROFILE ".claude\templates"),
(Join-Path $env:USERPROFILE ".claude\presets\domains"),
(Join-Path $env:USERPROFILE ".claude\presets\tech-stacks"),
(Join-Path $env:USERPROFILE ".claude\presets\workflows"),
(Join-Path $env:USERPROFILE ".claude\presets\jarvis-loop.json")
)
foreach ($p in $legacyPaths) {
Remove-LegacyPath $p
}
# Claude-specific runtime files (settings, hooks — CLAUDE.md is now a thin pointer)
$runtimeFiles = @("CLAUDE.md", "settings.json", "hooks-config.json", "context7-integration.md")
foreach ($rf in $runtimeFiles) {
$src = Join-Path $MosaicHome "runtime\claude\$rf"
if (-not (Test-Path $src)) { continue }
$dst = Join-Path $env:USERPROFILE ".claude\$rf"
Copy-FileManaged $src $dst
}
# OpenCode runtime adapter
$opencodeSrc = Join-Path $MosaicHome "runtime\opencode\AGENTS.md"
if (Test-Path $opencodeSrc) {
$opencodeDst = Join-Path $env:USERPROFILE ".config\opencode\AGENTS.md"
Copy-FileManaged $opencodeSrc $opencodeDst
}
# Codex runtime adapter
$codexSrc = Join-Path $MosaicHome "runtime\codex\instructions.md"
if (Test-Path $codexSrc) {
$codexDir = Join-Path $env:USERPROFILE ".codex"
if (-not (Test-Path $codexDir)) { New-Item -ItemType Directory -Path $codexDir -Force | Out-Null }
$codexDst = Join-Path $codexDir "instructions.md"
Copy-FileManaged $codexSrc $codexDst
}
$seqScript = Join-Path $MosaicHome "bin\mosaic-ensure-sequential-thinking.ps1"
if (Test-Path $seqScript) {
& $seqScript
}
Write-Host "[mosaic-link] Runtime assets synced (non-symlink mode)"
Write-Host "[mosaic-link] Canonical source: $MosaicHome"

View File

@@ -0,0 +1,90 @@
# mosaic-migrate-local-skills.ps1
# Migrates runtime-local skill directories to Mosaic-managed junctions.
# Uses directory junctions (no elevation required) with fallback to copies.
# PowerShell equivalent of mosaic-migrate-local-skills (bash).
$ErrorActionPreference = "Stop"
param(
[switch]$Apply,
[switch]$Help
)
$MosaicHome = if ($env:MOSAIC_HOME) { $env:MOSAIC_HOME } else { Join-Path $env:USERPROFILE ".config\mosaic" }
$LocalSkillsDir = Join-Path $MosaicHome "skills-local"
if ($Help) {
Write-Host @"
Usage: mosaic-migrate-local-skills.ps1 [-Apply] [-Help]
Migrate runtime-local skill directories (e.g. ~/.claude/skills/jarvis) to
Mosaic-managed skills by replacing local directories with junctions to
~/.config/mosaic/skills-local.
Default mode is dry-run.
"@
exit 0
}
if (-not (Test-Path $LocalSkillsDir)) {
Write-Host "[mosaic-local-skills] Missing local skills dir: $LocalSkillsDir" -ForegroundColor Red
exit 1
}
$skillRoots = @(
(Join-Path $env:USERPROFILE ".claude\skills"),
(Join-Path $env:USERPROFILE ".codex\skills"),
(Join-Path $env:USERPROFILE ".config\opencode\skills")
)
$count = 0
Get-ChildItem $LocalSkillsDir -Directory | ForEach-Object {
$name = $_.Name
$src = $_.FullName
foreach ($root in $skillRoots) {
if (-not (Test-Path $root)) { continue }
$target = Join-Path $root $name
# Already a junction/symlink — check if it points to the right place
$existing = Get-Item $target -Force -ErrorAction SilentlyContinue
if ($existing -and ($existing.Attributes -band [System.IO.FileAttributes]::ReparsePoint)) {
$currentTarget = $existing.Target
if ($currentTarget -and ($currentTarget -eq $src -or (Resolve-Path $currentTarget -ErrorAction SilentlyContinue).Path -eq (Resolve-Path $src -ErrorAction SilentlyContinue).Path)) {
continue
}
}
# Only migrate local directories containing SKILL.md
if ((Test-Path $target -PathType Container) -and
(Test-Path (Join-Path $target "SKILL.md")) -and
-not ($existing -and ($existing.Attributes -band [System.IO.FileAttributes]::ReparsePoint))) {
$count++
if ($Apply) {
$stamp = Get-Date -Format "yyyyMMddHHmmss"
Rename-Item $target "$target.mosaic-bak-$stamp"
try {
New-Item -ItemType Junction -Path $target -Target $src -ErrorAction Stop | Out-Null
Write-Host "[mosaic-local-skills] migrated: $target -> $src"
}
catch {
Write-Host "[mosaic-local-skills] Junction failed for $name, falling back to copy"
Copy-Item $src $target -Recurse -Force
Write-Host "[mosaic-local-skills] copied: $target <- $src"
}
}
else {
Write-Host "[mosaic-local-skills] would migrate: $target -> $src"
}
}
}
}
if ($Apply) {
Write-Host "[mosaic-local-skills] complete: migrated=$count"
}
else {
Write-Host "[mosaic-local-skills] dry-run: migratable=$count"
Write-Host "[mosaic-local-skills] re-run with -Apply to migrate"
}

View File

@@ -0,0 +1,65 @@
# mosaic-release-upgrade.ps1 — Upgrade installed Mosaic framework release (Windows)
#
# Usage:
# mosaic-release-upgrade.ps1
# mosaic-release-upgrade.ps1 -Ref main -Keep
# mosaic-release-upgrade.ps1 -Ref v0.2.0 -Overwrite -Yes
#
param(
[string]$Ref = $(if ($env:MOSAIC_BOOTSTRAP_REF) { $env:MOSAIC_BOOTSTRAP_REF } else { "main" }),
[switch]$Keep,
[switch]$Overwrite,
[switch]$Yes,
[switch]$DryRun
)
$ErrorActionPreference = "Stop"
$MosaicHome = if ($env:MOSAIC_HOME) { $env:MOSAIC_HOME } else { Join-Path $env:USERPROFILE ".config\mosaic" }
$RemoteInstallerUrl = if ($env:MOSAIC_REMOTE_INSTALL_URL) {
$env:MOSAIC_REMOTE_INSTALL_URL
} else {
"https://git.mosaicstack.dev/mosaic/bootstrap/raw/branch/main/remote-install.ps1"
}
$installMode = if ($Overwrite) { "overwrite" } elseif ($Keep) { "keep" } elseif ($env:MOSAIC_INSTALL_MODE) { $env:MOSAIC_INSTALL_MODE } else { "keep" }
if ($installMode -notin @("keep", "overwrite")) {
Write-Host "[mosaic-release-upgrade] Invalid install mode: $installMode" -ForegroundColor Red
exit 1
}
$currentVersion = "unknown"
$mosaicCmd = Join-Path $MosaicHome "bin\mosaic.ps1"
if (Test-Path $mosaicCmd) {
try {
$currentVersion = (& $mosaicCmd --version) -replace '^mosaic\s+', ''
}
catch {
$currentVersion = "unknown"
}
}
Write-Host "[mosaic-release-upgrade] Current version: $currentVersion"
Write-Host "[mosaic-release-upgrade] Target ref: $Ref"
Write-Host "[mosaic-release-upgrade] Install mode: $installMode"
Write-Host "[mosaic-release-upgrade] Installer URL: $RemoteInstallerUrl"
if ($DryRun) {
Write-Host "[mosaic-release-upgrade] Dry run: no changes applied."
exit 0
}
if (-not $Yes) {
$confirmation = Read-Host "Proceed with Mosaic release upgrade? [y/N]"
if ($confirmation -notin @("y", "Y", "yes", "YES")) {
Write-Host "[mosaic-release-upgrade] Aborted."
exit 1
}
}
$env:MOSAIC_BOOTSTRAP_REF = $Ref
$env:MOSAIC_INSTALL_MODE = $installMode
$env:MOSAIC_HOME = $MosaicHome
Invoke-RestMethod -Uri $RemoteInstallerUrl | Invoke-Expression

View File

@@ -0,0 +1,126 @@
# mosaic-sync-skills.ps1
# Syncs canonical skills and links them into agent runtime skill directories.
# Uses directory junctions (no elevation required) with fallback to copies.
# PowerShell equivalent of mosaic-sync-skills (bash).
$ErrorActionPreference = "Stop"
param(
[switch]$LinkOnly,
[switch]$NoLink,
[switch]$Help
)
$MosaicHome = if ($env:MOSAIC_HOME) { $env:MOSAIC_HOME } else { Join-Path $env:USERPROFILE ".config\mosaic" }
$SkillsRepoUrl = if ($env:MOSAIC_SKILLS_REPO_URL) { $env:MOSAIC_SKILLS_REPO_URL } else { "https://git.mosaicstack.dev/mosaic/agent-skills.git" }
$SkillsRepoDir = if ($env:MOSAIC_SKILLS_REPO_DIR) { $env:MOSAIC_SKILLS_REPO_DIR } else { Join-Path $MosaicHome "sources\agent-skills" }
$MosaicSkillsDir = Join-Path $MosaicHome "skills"
$MosaicLocalSkillsDir = Join-Path $MosaicHome "skills-local"
if ($Help) {
Write-Host @"
Usage: mosaic-sync-skills.ps1 [-LinkOnly] [-NoLink] [-Help]
Sync canonical skills into ~/.config/mosaic/skills and link all Mosaic skills
into runtime skill directories using directory junctions.
Options:
-LinkOnly Skip git clone/pull and only relink
-NoLink Sync canonical skills but do not update runtime links
-Help Show help
"@
exit 0
}
foreach ($d in @($MosaicHome, $MosaicSkillsDir, $MosaicLocalSkillsDir)) {
if (-not (Test-Path $d)) { New-Item -ItemType Directory -Path $d -Force | Out-Null }
}
# Fetch skills from git
if (-not $LinkOnly) {
if (Test-Path (Join-Path $SkillsRepoDir ".git")) {
Write-Host "[mosaic-skills] Updating skills source: $SkillsRepoDir"
git -C $SkillsRepoDir pull --rebase
}
else {
Write-Host "[mosaic-skills] Cloning skills source to: $SkillsRepoDir"
$parentDir = Split-Path $SkillsRepoDir -Parent
if (-not (Test-Path $parentDir)) { New-Item -ItemType Directory -Path $parentDir -Force | Out-Null }
git clone $SkillsRepoUrl $SkillsRepoDir
}
$sourceSkillsDir = Join-Path $SkillsRepoDir "skills"
if (-not (Test-Path $sourceSkillsDir)) {
Write-Host "[mosaic-skills] Missing source skills dir: $sourceSkillsDir" -ForegroundColor Red
exit 1
}
# Sync: remove old, copy new
if (Test-Path $MosaicSkillsDir) {
Get-ChildItem $MosaicSkillsDir | Remove-Item -Recurse -Force
}
Copy-Item "$sourceSkillsDir\*" $MosaicSkillsDir -Recurse -Force
}
if (-not (Test-Path $MosaicSkillsDir)) {
Write-Host "[mosaic-skills] Canonical skills dir missing: $MosaicSkillsDir" -ForegroundColor Red
exit 1
}
if ($NoLink) {
Write-Host "[mosaic-skills] Canonical sync completed (link update skipped)"
exit 0
}
function Link-SkillIntoTarget {
param([string]$SkillPath, [string]$TargetDir)
$name = Split-Path $SkillPath -Leaf
if ($name.StartsWith(".")) { return }
$linkPath = Join-Path $TargetDir $name
# Already a junction/symlink — recreate
$existing = Get-Item $linkPath -Force -ErrorAction SilentlyContinue
if ($existing -and ($existing.Attributes -band [System.IO.FileAttributes]::ReparsePoint)) {
Remove-Item $linkPath -Force
}
elseif ($existing) {
Write-Host "[mosaic-skills] Preserve existing runtime-specific entry: $linkPath"
return
}
# Try junction first, fall back to copy
try {
New-Item -ItemType Junction -Path $linkPath -Target $SkillPath -ErrorAction Stop | Out-Null
}
catch {
Write-Host "[mosaic-skills] Junction failed for $name, falling back to copy"
Copy-Item $SkillPath $linkPath -Recurse -Force
}
}
$linkTargets = @(
(Join-Path $env:USERPROFILE ".claude\skills"),
(Join-Path $env:USERPROFILE ".codex\skills"),
(Join-Path $env:USERPROFILE ".config\opencode\skills")
)
foreach ($target in $linkTargets) {
if (-not (Test-Path $target)) { New-Item -ItemType Directory -Path $target -Force | Out-Null }
# Link canonical skills
Get-ChildItem $MosaicSkillsDir -Directory | ForEach-Object {
Link-SkillIntoTarget $_.FullName $target
}
# Link local skills
if (Test-Path $MosaicLocalSkillsDir) {
Get-ChildItem $MosaicLocalSkillsDir -Directory | ForEach-Object {
Link-SkillIntoTarget $_.FullName $target
}
}
Write-Host "[mosaic-skills] Linked skills into: $target"
}
Write-Host "[mosaic-skills] Complete"

View File

@@ -0,0 +1,437 @@
# 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 <runtime> [args...] Launch runtime in dangerous-permissions mode
# mosaic --yolo <runtime> [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 <command> [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 <runtime> [args...] Dangerous mode for claude|codex|opencode
--yolo <runtime> [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 <path> 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: irm https://git.mosaicstack.dev/mosaic/bootstrap/raw/branch/main/remote-install.ps1 | iex"
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
}
}