chore: sync local Mosaic changes
This commit is contained in:
204
bin/mosaic
204
bin/mosaic
@@ -3,16 +3,19 @@ set -euo pipefail
|
||||
|
||||
# mosaic — Unified agent launcher and management CLI
|
||||
#
|
||||
# AGENTS.md is the single source of truth for all agent sessions.
|
||||
# The launcher injects it into every runtime consistently.
|
||||
# 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 AGENTS.md injected
|
||||
# mosaic opencode [args...] Launch OpenCode with AGENTS.md injected
|
||||
# mosaic codex [args...] Launch Codex with AGENTS.md injected
|
||||
# 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
|
||||
# mosaic seq [subcommand] sequential-thinking MCP management (check/fix/start)
|
||||
# mosaic bootstrap <path> Bootstrap a repo
|
||||
# mosaic upgrade release Upgrade installed Mosaic release
|
||||
# mosaic upgrade check Check release upgrade status (no changes)
|
||||
@@ -28,14 +31,20 @@ mosaic $VERSION — Unified agent launcher
|
||||
Usage: mosaic <command> [args...]
|
||||
|
||||
Agent Launchers:
|
||||
claude [args...] Launch Claude Code with AGENTS.md injected
|
||||
opencode [args...] Launch OpenCode with AGENTS.md injected
|
||||
codex [args...] Launch Codex with AGENTS.md injected
|
||||
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
|
||||
seq [subcommand] sequential-thinking MCP management:
|
||||
check [--runtime <r>] [--strict]
|
||||
fix [--runtime <r>]
|
||||
start
|
||||
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)
|
||||
@@ -83,14 +92,79 @@ check_runtime() {
|
||||
fi
|
||||
}
|
||||
|
||||
# Ensure AGENTS.md is present at the runtime's native config path.
|
||||
# Used for runtimes that don't support CLI prompt injection.
|
||||
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" ;;
|
||||
*)
|
||||
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
|
||||
|
||||
cat <<'EOF'
|
||||
# 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.
|
||||
|
||||
EOF
|
||||
|
||||
cat "$MOSAIC_HOME/AGENTS.md"
|
||||
printf '\n\n# Runtime-Specific Contract\n\n'
|
||||
cat "$runtime_file"
|
||||
}
|
||||
|
||||
# Ensure runtime contract is present at the runtime's native config path.
|
||||
# Used for runtimes that do not support CLI prompt injection.
|
||||
ensure_runtime_config() {
|
||||
local src="$MOSAIC_HOME/AGENTS.md"
|
||||
local dst="$1"
|
||||
local runtime="$1"
|
||||
local dst="$2"
|
||||
local tmp
|
||||
tmp="$(mktemp)"
|
||||
mkdir -p "$(dirname "$dst")"
|
||||
if ! cmp -s "$src" "$dst" 2>/dev/null; then
|
||||
cp "$src" "$dst"
|
||||
build_runtime_prompt "$runtime" > "$tmp"
|
||||
if ! cmp -s "$tmp" "$dst" 2>/dev/null; then
|
||||
mv "$tmp" "$dst"
|
||||
else
|
||||
rm -f "$tmp"
|
||||
fi
|
||||
}
|
||||
|
||||
@@ -100,12 +174,13 @@ launch_claude() {
|
||||
check_agents_md
|
||||
check_soul
|
||||
check_runtime "claude"
|
||||
check_sequential_thinking "claude"
|
||||
|
||||
# Claude supports --append-system-prompt for direct injection
|
||||
local agents_content
|
||||
agents_content="$(cat "$MOSAIC_HOME/AGENTS.md")"
|
||||
local runtime_prompt
|
||||
runtime_prompt="$(build_runtime_prompt "claude")"
|
||||
echo "[mosaic] Launching Claude Code..."
|
||||
exec claude --append-system-prompt "$agents_content" "$@"
|
||||
exec claude --append-system-prompt "$runtime_prompt" "$@"
|
||||
}
|
||||
|
||||
launch_opencode() {
|
||||
@@ -113,9 +188,10 @@ launch_opencode() {
|
||||
check_agents_md
|
||||
check_soul
|
||||
check_runtime "opencode"
|
||||
check_sequential_thinking "opencode"
|
||||
|
||||
# OpenCode reads from ~/.config/opencode/AGENTS.md — copy canonical version there
|
||||
ensure_runtime_config "$HOME/.config/opencode/AGENTS.md"
|
||||
# OpenCode reads from ~/.config/opencode/AGENTS.md
|
||||
ensure_runtime_config "opencode" "$HOME/.config/opencode/AGENTS.md"
|
||||
echo "[mosaic] Launching OpenCode..."
|
||||
exec opencode "$@"
|
||||
}
|
||||
@@ -125,13 +201,69 @@ launch_codex() {
|
||||
check_agents_md
|
||||
check_soul
|
||||
check_runtime "codex"
|
||||
check_sequential_thinking "codex"
|
||||
|
||||
# Codex reads from ~/.codex/instructions.md — copy canonical version there
|
||||
ensure_runtime_config "$HOME/.codex/instructions.md"
|
||||
# Codex reads from ~/.codex/instructions.md
|
||||
ensure_runtime_config "codex" "$HOME/.codex/instructions.md"
|
||||
echo "[mosaic] Launching Codex..."
|
||||
exec codex "$@"
|
||||
}
|
||||
|
||||
launch_yolo() {
|
||||
if [[ $# -eq 0 ]]; then
|
||||
echo "[mosaic] ERROR: yolo requires a runtime (claude|codex|opencode)." >&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")"
|
||||
echo "[mosaic] Launching Claude Code in YOLO mode (dangerous permissions enabled)..."
|
||||
exec claude --dangerously-skip-permissions --append-system-prompt "$runtime_prompt" "$@"
|
||||
;;
|
||||
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"
|
||||
echo "[mosaic] Launching Codex in YOLO mode (dangerous permissions enabled)..."
|
||||
exec codex --dangerously-bypass-approvals-and-sandbox "$@"
|
||||
;;
|
||||
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"
|
||||
echo "[mosaic] Launching OpenCode in YOLO mode..."
|
||||
exec opencode "$@"
|
||||
;;
|
||||
*)
|
||||
echo "[mosaic] ERROR: Unsupported yolo runtime '$runtime'. Use claude|codex|opencode." >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# Delegate to existing scripts
|
||||
run_init() {
|
||||
check_mosaic_home
|
||||
@@ -148,6 +280,34 @@ run_sync() {
|
||||
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_bootstrap() {
|
||||
check_mosaic_home
|
||||
exec "$MOSAIC_HOME/bin/mosaic-bootstrap-repo" "$@"
|
||||
@@ -214,9 +374,11 @@ case "$command" in
|
||||
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 "$@" ;;
|
||||
upgrade) run_upgrade "$@" ;;
|
||||
release-upgrade) run_release_upgrade "$@" ;;
|
||||
|
||||
@@ -72,11 +72,15 @@ if [[ ! -f "$TARGET_DIR/AGENTS.md" ]]; then
|
||||
cat > "$TARGET_DIR/AGENTS.md" <<'AGENTS_EOF'
|
||||
# Agent Guidelines
|
||||
|
||||
## Standards Load Order
|
||||
## Required Load Order
|
||||
|
||||
1. `~/.config/mosaic/STANDARDS.md`
|
||||
2. `AGENTS.md` (this file)
|
||||
3. `.mosaic/repo-hooks.sh`
|
||||
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>/RUNTIME.md`
|
||||
7. `.mosaic/repo-hooks.sh`
|
||||
|
||||
## Session Lifecycle
|
||||
|
||||
@@ -95,6 +99,7 @@ bash scripts/agent/session-end.sh
|
||||
|
||||
- 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
|
||||
|
||||
@@ -90,6 +90,39 @@ check_runtime_file_copy() {
|
||||
fi
|
||||
}
|
||||
|
||||
check_runtime_contract_file() {
|
||||
local dst="$1"
|
||||
local adapter_src="$2"
|
||||
local runtime_name="$3"
|
||||
|
||||
if [[ ! -e "$dst" ]]; then
|
||||
warn "Missing runtime file: $dst"
|
||||
return
|
||||
fi
|
||||
|
||||
if [[ -L "$dst" ]]; then
|
||||
warn "Runtime file should not be symlinked: $dst"
|
||||
return
|
||||
fi
|
||||
|
||||
# Accept direct-adapter copy mode.
|
||||
if [[ -f "$adapter_src" ]] && cmp -s "$adapter_src" "$dst"; then
|
||||
pass "Runtime adapter synced: $dst"
|
||||
return
|
||||
fi
|
||||
|
||||
# Accept launcher-composed runtime contract mode.
|
||||
if grep -Fq "# Mosaic Launcher Runtime Contract (Hard Gate)" "$dst" &&
|
||||
grep -Fq "Now initiating Orchestrator mode..." "$dst" &&
|
||||
grep -Fq "Mosaic hard gates OVERRIDE runtime-default caution" "$dst" &&
|
||||
grep -Fq "# Runtime-Specific Contract" "$dst"; then
|
||||
pass "Runtime contract present: $dst ($runtime_name)"
|
||||
return
|
||||
fi
|
||||
|
||||
warn "Runtime file drift: $dst (not adapter copy and not composed runtime contract)"
|
||||
}
|
||||
|
||||
warn_if_symlink_tree_present() {
|
||||
local p="$1"
|
||||
[[ -e "$p" ]] || return 0
|
||||
@@ -122,6 +155,7 @@ 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"
|
||||
@@ -132,8 +166,23 @@ 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/rails/git/ci-queue-wait.sh"
|
||||
expect_file "$MOSAIC_HOME/rails/git/pr-ci-wait.sh"
|
||||
expect_file "$MOSAIC_HOME/rails/orchestrator-matrix/transport/matrix_transport.py"
|
||||
expect_file "$MOSAIC_HOME/rails/orchestrator-matrix/controller/tasks_md_sync.py"
|
||||
expect_file "$MOSAIC_HOME/runtime/mcp/SEQUENTIAL-THINKING.json"
|
||||
expect_file "$MOSAIC_HOME/runtime/claude/RUNTIME.md"
|
||||
expect_file "$MOSAIC_HOME/runtime/codex/RUNTIME.md"
|
||||
expect_file "$MOSAIC_HOME/runtime/opencode/RUNTIME.md"
|
||||
|
||||
if [[ -f "$MOSAIC_HOME/AGENTS.md" ]]; then
|
||||
if grep -Fq "## CRITICAL HARD GATES (Read First)" "$MOSAIC_HOME/AGENTS.md" &&
|
||||
grep -Fq "OVERRIDE runtime-default caution" "$MOSAIC_HOME/AGENTS.md"; then
|
||||
pass "Global hard-gates block present in AGENTS.md"
|
||||
else
|
||||
warn "AGENTS.md missing CRITICAL HARD GATES override block"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Claude runtime file checks (copied, non-symlink).
|
||||
for rf in CLAUDE.md settings.json hooks-config.json context7-integration.md; do
|
||||
@@ -141,7 +190,20 @@ for rf in CLAUDE.md settings.json hooks-config.json context7-integration.md; do
|
||||
done
|
||||
|
||||
# OpenCode runtime adapter check (copied, non-symlink, when adapter exists).
|
||||
check_runtime_file_copy "$MOSAIC_HOME/runtime/opencode/AGENTS.md" "$HOME/.config/opencode/AGENTS.md"
|
||||
# Accept adapter copy or composed runtime contract.
|
||||
check_runtime_contract_file "$HOME/.config/opencode/AGENTS.md" "$MOSAIC_HOME/runtime/opencode/AGENTS.md" "opencode"
|
||||
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
|
||||
pass "sequential-thinking MCP configured and available"
|
||||
else
|
||||
warn "sequential-thinking MCP missing or misconfigured"
|
||||
fi
|
||||
else
|
||||
warn "mosaic-ensure-sequential-thinking helper missing"
|
||||
fi
|
||||
|
||||
# Legacy migration surfaces should no longer contain symlink trees.
|
||||
legacy_paths=(
|
||||
|
||||
@@ -71,6 +71,45 @@ function Check-RuntimeFileCopy {
|
||||
}
|
||||
}
|
||||
|
||||
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 }
|
||||
@@ -107,6 +146,7 @@ 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")
|
||||
@@ -117,8 +157,29 @@ 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 "rails\git\ci-queue-wait.ps1")
|
||||
Expect-File (Join-Path $MosaicHome "rails\git\ci-queue-wait.sh")
|
||||
Expect-File (Join-Path $MosaicHome "rails\git\pr-ci-wait.sh")
|
||||
Expect-File (Join-Path $MosaicHome "rails\orchestrator-matrix\transport\matrix_transport.py")
|
||||
Expect-File (Join-Path $MosaicHome "rails\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")
|
||||
@@ -126,8 +187,24 @@ foreach ($rf in $runtimeFiles) {
|
||||
Check-RuntimeFileCopy (Join-Path $MosaicHome "runtime\claude\$rf") (Join-Path $env:USERPROFILE ".claude\$rf")
|
||||
}
|
||||
|
||||
# OpenCode runtime adapter
|
||||
Check-RuntimeFileCopy (Join-Path $MosaicHome "runtime\opencode\AGENTS.md") (Join-Path $env:USERPROFILE ".config\opencode\AGENTS.md")
|
||||
# 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 = @(
|
||||
|
||||
262
bin/mosaic-ensure-sequential-thinking
Executable file
262
bin/mosaic-ensure-sequential-thinking
Executable file
@@ -0,0 +1,262 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
MOSAIC_HOME="${MOSAIC_HOME:-$HOME/.config/mosaic}"
|
||||
MODE="apply"
|
||||
RUNTIME="all"
|
||||
STRICT_CHECK=0
|
||||
|
||||
PKG="@modelcontextprotocol/server-sequential-thinking"
|
||||
|
||||
err() { echo "[mosaic-seq] ERROR: $*" >&2; }
|
||||
log() { echo "[mosaic-seq] $*"; }
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--check)
|
||||
MODE="check"
|
||||
shift
|
||||
;;
|
||||
--runtime)
|
||||
if [[ $# -lt 2 ]]; then
|
||||
err "--runtime requires a value: claude|codex|opencode|all"
|
||||
exit 2
|
||||
fi
|
||||
RUNTIME="$2"
|
||||
shift 2
|
||||
;;
|
||||
--strict)
|
||||
STRICT_CHECK=1
|
||||
shift
|
||||
;;
|
||||
*)
|
||||
err "Unknown argument: $1"
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
case "$RUNTIME" in
|
||||
all|claude|codex|opencode) ;;
|
||||
*)
|
||||
err "Invalid runtime: $RUNTIME (expected claude|codex|opencode|all)"
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
|
||||
require_binary() {
|
||||
local name="$1"
|
||||
if ! command -v "$name" >/dev/null 2>&1; then
|
||||
err "Required binary missing: $name"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
check_software() {
|
||||
require_binary node
|
||||
require_binary npx
|
||||
}
|
||||
|
||||
warm_package() {
|
||||
local timeout_sec="${MOSAIC_SEQ_WARM_TIMEOUT_SEC:-15}"
|
||||
if command -v timeout >/dev/null 2>&1; then
|
||||
timeout "$timeout_sec" npx -y "$PKG" --help >/dev/null 2>&1
|
||||
else
|
||||
npx -y "$PKG" --help >/dev/null 2>&1
|
||||
fi
|
||||
}
|
||||
|
||||
check_claude_config() {
|
||||
python3 - <<'PY'
|
||||
import json
|
||||
from pathlib import Path
|
||||
p = Path.home() / ".claude" / "settings.json"
|
||||
if not p.exists():
|
||||
raise SystemExit(1)
|
||||
try:
|
||||
data = json.loads(p.read_text(encoding="utf-8"))
|
||||
except Exception:
|
||||
raise SystemExit(1)
|
||||
mcp = data.get("mcpServers")
|
||||
if not isinstance(mcp, dict):
|
||||
raise SystemExit(1)
|
||||
entry = mcp.get("sequential-thinking")
|
||||
if not isinstance(entry, dict):
|
||||
raise SystemExit(1)
|
||||
if entry.get("command") != "npx":
|
||||
raise SystemExit(1)
|
||||
args = entry.get("args")
|
||||
if args != ["-y", "@modelcontextprotocol/server-sequential-thinking"]:
|
||||
raise SystemExit(1)
|
||||
PY
|
||||
}
|
||||
|
||||
apply_claude_config() {
|
||||
python3 - <<'PY'
|
||||
import json
|
||||
from pathlib import Path
|
||||
p = Path.home() / ".claude" / "settings.json"
|
||||
p.parent.mkdir(parents=True, exist_ok=True)
|
||||
if p.exists():
|
||||
try:
|
||||
data = json.loads(p.read_text(encoding="utf-8"))
|
||||
except Exception:
|
||||
data = {}
|
||||
else:
|
||||
data = {}
|
||||
mcp = data.get("mcpServers")
|
||||
if not isinstance(mcp, dict):
|
||||
mcp = {}
|
||||
mcp["sequential-thinking"] = {
|
||||
"command": "npx",
|
||||
"args": ["-y", "@modelcontextprotocol/server-sequential-thinking"]
|
||||
}
|
||||
data["mcpServers"] = mcp
|
||||
p.write_text(json.dumps(data, indent=2) + "\n", encoding="utf-8")
|
||||
PY
|
||||
}
|
||||
|
||||
check_codex_config() {
|
||||
local cfg="$HOME/.codex/config.toml"
|
||||
[[ -f "$cfg" ]] || return 1
|
||||
grep -Eq '^\[mcp_servers\.(sequential-thinking|sequential_thinking)\]' "$cfg" && \
|
||||
grep -q '^command = "npx"' "$cfg" && \
|
||||
grep -q '@modelcontextprotocol/server-sequential-thinking' "$cfg"
|
||||
}
|
||||
|
||||
apply_codex_config() {
|
||||
local cfg="$HOME/.codex/config.toml"
|
||||
mkdir -p "$(dirname "$cfg")"
|
||||
[[ -f "$cfg" ]] || touch "$cfg"
|
||||
|
||||
local tmp
|
||||
tmp="$(mktemp)"
|
||||
awk '
|
||||
BEGIN { skip = 0 }
|
||||
/^\[mcp_servers\.(sequential-thinking|sequential_thinking)\]/ { skip = 1; next }
|
||||
skip && /^\[/ { skip = 0 }
|
||||
!skip { print }
|
||||
' "$cfg" > "$tmp"
|
||||
mv "$tmp" "$cfg"
|
||||
|
||||
{
|
||||
echo ""
|
||||
echo "[mcp_servers.sequential-thinking]"
|
||||
echo "command = \"npx\""
|
||||
echo "args = [\"-y\", \"@modelcontextprotocol/server-sequential-thinking\"]"
|
||||
} >> "$cfg"
|
||||
}
|
||||
|
||||
check_opencode_config() {
|
||||
python3 - <<'PY'
|
||||
import json
|
||||
from pathlib import Path
|
||||
p = Path.home() / ".config" / "opencode" / "config.json"
|
||||
if not p.exists():
|
||||
raise SystemExit(1)
|
||||
try:
|
||||
data = json.loads(p.read_text(encoding="utf-8"))
|
||||
except Exception:
|
||||
raise SystemExit(1)
|
||||
mcp = data.get("mcp")
|
||||
if not isinstance(mcp, dict):
|
||||
raise SystemExit(1)
|
||||
entry = mcp.get("sequential-thinking")
|
||||
if not isinstance(entry, dict):
|
||||
raise SystemExit(1)
|
||||
if entry.get("type") != "local":
|
||||
raise SystemExit(1)
|
||||
if entry.get("command") != ["npx", "-y", "@modelcontextprotocol/server-sequential-thinking"]:
|
||||
raise SystemExit(1)
|
||||
if entry.get("enabled") is not True:
|
||||
raise SystemExit(1)
|
||||
PY
|
||||
}
|
||||
|
||||
apply_opencode_config() {
|
||||
python3 - <<'PY'
|
||||
import json
|
||||
from pathlib import Path
|
||||
p = Path.home() / ".config" / "opencode" / "config.json"
|
||||
p.parent.mkdir(parents=True, exist_ok=True)
|
||||
if p.exists():
|
||||
try:
|
||||
data = json.loads(p.read_text(encoding="utf-8"))
|
||||
except Exception:
|
||||
data = {}
|
||||
else:
|
||||
data = {}
|
||||
mcp = data.get("mcp")
|
||||
if not isinstance(mcp, dict):
|
||||
mcp = {}
|
||||
mcp["sequential-thinking"] = {
|
||||
"type": "local",
|
||||
"command": ["npx", "-y", "@modelcontextprotocol/server-sequential-thinking"],
|
||||
"enabled": True
|
||||
}
|
||||
data["mcp"] = mcp
|
||||
p.write_text(json.dumps(data, indent=2) + "\n", encoding="utf-8")
|
||||
PY
|
||||
}
|
||||
|
||||
check_runtime_config() {
|
||||
case "$RUNTIME" in
|
||||
all)
|
||||
check_claude_config
|
||||
check_codex_config
|
||||
check_opencode_config
|
||||
;;
|
||||
claude)
|
||||
check_claude_config
|
||||
;;
|
||||
codex)
|
||||
check_codex_config
|
||||
;;
|
||||
opencode)
|
||||
check_opencode_config
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
apply_runtime_config() {
|
||||
case "$RUNTIME" in
|
||||
all)
|
||||
apply_claude_config
|
||||
apply_codex_config
|
||||
apply_opencode_config
|
||||
;;
|
||||
claude)
|
||||
apply_claude_config
|
||||
;;
|
||||
codex)
|
||||
apply_codex_config
|
||||
;;
|
||||
opencode)
|
||||
apply_opencode_config
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
if [[ "$MODE" == "check" ]]; then
|
||||
check_software
|
||||
check_runtime_config
|
||||
|
||||
# Runtime launch checks should be local/fast by default.
|
||||
if [[ "$STRICT_CHECK" -eq 1 || "${MOSAIC_SEQ_CHECK_WARM:-0}" == "1" ]]; then
|
||||
if ! warm_package; then
|
||||
err "sequential-thinking package warm-up failed in strict mode"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
log "sequential-thinking MCP is configured and available (${RUNTIME})"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
check_software
|
||||
if ! warm_package; then
|
||||
err "Unable to warm sequential-thinking package (npx timeout/failure)"
|
||||
exit 1
|
||||
fi
|
||||
apply_runtime_config
|
||||
log "sequential-thinking MCP configured (${RUNTIME})"
|
||||
114
bin/mosaic-ensure-sequential-thinking.ps1
Executable file
114
bin/mosaic-ensure-sequential-thinking.ps1
Executable file
@@ -0,0 +1,114 @@
|
||||
# mosaic-ensure-sequential-thinking.ps1
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
param(
|
||||
[switch]$Check
|
||||
)
|
||||
|
||||
$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"
|
||||
@@ -62,7 +62,7 @@ legacy_paths=(
|
||||
"$HOME/.claude/presets/domains"
|
||||
"$HOME/.claude/presets/tech-stacks"
|
||||
"$HOME/.claude/presets/workflows"
|
||||
"$HOME/.claude/presets/jarvis-ralph.json"
|
||||
"$HOME/.claude/presets/jarvis-loop.json"
|
||||
)
|
||||
|
||||
for p in "${legacy_paths[@]}"; do
|
||||
@@ -93,5 +93,9 @@ if [[ -f "$codex_adapter" ]]; then
|
||||
copy_file_managed "$codex_adapter" "$HOME/.codex/instructions.md"
|
||||
fi
|
||||
|
||||
if [[ -x "$MOSAIC_HOME/bin/mosaic-ensure-sequential-thinking" ]]; then
|
||||
"$MOSAIC_HOME/bin/mosaic-ensure-sequential-thinking"
|
||||
fi
|
||||
|
||||
echo "[mosaic-link] Runtime assets synced (non-symlink mode)"
|
||||
echo "[mosaic-link] Canonical source: $MOSAIC_HOME"
|
||||
|
||||
@@ -70,7 +70,7 @@ $legacyPaths = @(
|
||||
(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-ralph.json")
|
||||
(Join-Path $env:USERPROFILE ".claude\presets\jarvis-loop.json")
|
||||
)
|
||||
|
||||
foreach ($p in $legacyPaths) {
|
||||
@@ -102,5 +102,10 @@ if (Test-Path $codexSrc) {
|
||||
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"
|
||||
|
||||
@@ -121,6 +121,38 @@ link_skill_into_target() {
|
||||
ln -s "$skill_path" "$link_path"
|
||||
}
|
||||
|
||||
is_mosaic_skill_name() {
|
||||
local name="$1"
|
||||
[[ -d "$MOSAIC_SKILLS_DIR/$name" ]] && return 0
|
||||
[[ -d "$MOSAIC_LOCAL_SKILLS_DIR/$name" ]] && return 0
|
||||
return 1
|
||||
}
|
||||
|
||||
prune_stale_links_in_target() {
|
||||
local target_dir="$1"
|
||||
|
||||
while IFS= read -r -d '' link_path; do
|
||||
local name resolved
|
||||
name="$(basename "$link_path")"
|
||||
|
||||
if is_mosaic_skill_name "$name"; then
|
||||
continue
|
||||
fi
|
||||
|
||||
resolved="$(readlink -f "$link_path" 2>/dev/null || true)"
|
||||
if [[ -z "$resolved" ]]; then
|
||||
rm -f "$link_path"
|
||||
echo "[mosaic-skills] Removed stale broken skill link: $link_path"
|
||||
continue
|
||||
fi
|
||||
|
||||
if [[ "$resolved" == "$MOSAIC_HOME/"* ]]; then
|
||||
rm -f "$link_path"
|
||||
echo "[mosaic-skills] Removed stale retired skill link: $link_path"
|
||||
fi
|
||||
done < <(find "$target_dir" -mindepth 1 -maxdepth 1 -type l -print0)
|
||||
}
|
||||
|
||||
for target in "${link_targets[@]}"; do
|
||||
mkdir -p "$target"
|
||||
|
||||
@@ -131,6 +163,8 @@ for target in "${link_targets[@]}"; do
|
||||
continue
|
||||
fi
|
||||
|
||||
prune_stale_links_in_target "$target"
|
||||
|
||||
while IFS= read -r -d '' skill; do
|
||||
link_skill_into_target "$skill" "$target"
|
||||
done < <(find "$MOSAIC_SKILLS_DIR" -mindepth 1 -maxdepth 1 -type d -print0)
|
||||
|
||||
164
bin/mosaic.ps1
164
bin/mosaic.ps1
@@ -1,12 +1,14 @@
|
||||
# mosaic.ps1 — Unified agent launcher and management CLI (Windows)
|
||||
#
|
||||
# AGENTS.md is the single source of truth for all agent sessions.
|
||||
# The launcher injects it into every runtime consistently.
|
||||
# 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 AGENTS.md injected
|
||||
# mosaic opencode [args...] Launch OpenCode with AGENTS.md injected
|
||||
# mosaic codex [args...] Launch Codex with AGENTS.md injected
|
||||
# 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
|
||||
@@ -22,9 +24,11 @@ mosaic $Version - Unified agent launcher
|
||||
Usage: mosaic <command> [args...]
|
||||
|
||||
Agent Launchers:
|
||||
claude [args...] Launch Claude Code with AGENTS.md injected
|
||||
opencode [args...] Launch OpenCode with AGENTS.md injected
|
||||
codex [args...] Launch Codex with AGENTS.md injected
|
||||
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)
|
||||
@@ -76,15 +80,136 @@ function Assert-Runtime {
|
||||
}
|
||||
}
|
||||
|
||||
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-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.
|
||||
|
||||
'@
|
||||
|
||||
$agentsContent = Get-Content (Join-Path $MosaicHome "AGENTS.md") -Raw
|
||||
$runtimeContent = Get-Content $runtimeFile -Raw
|
||||
return "$launcherContract`n$agentsContent`n`n# Runtime-Specific Contract`n`n$runtimeContent"
|
||||
}
|
||||
|
||||
function Ensure-RuntimeConfig {
|
||||
param([string]$Dst)
|
||||
$src = Join-Path $MosaicHome "AGENTS.md"
|
||||
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 }
|
||||
$srcHash = (Get-FileHash $src -Algorithm SHA256).Hash
|
||||
|
||||
$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 $src $Dst -Force
|
||||
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")
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -102,8 +227,9 @@ switch ($command) {
|
||||
Assert-AgentsMd
|
||||
Assert-Soul
|
||||
Assert-Runtime "claude"
|
||||
Assert-SequentialThinking
|
||||
# Claude supports --append-system-prompt for direct injection
|
||||
$agentsContent = Get-Content (Join-Path $MosaicHome "AGENTS.md") -Raw
|
||||
$agentsContent = Get-RuntimePrompt -Runtime "claude"
|
||||
Write-Host "[mosaic] Launching Claude Code..."
|
||||
& claude --append-system-prompt $agentsContent @remaining
|
||||
}
|
||||
@@ -112,8 +238,9 @@ switch ($command) {
|
||||
Assert-AgentsMd
|
||||
Assert-Soul
|
||||
Assert-Runtime "opencode"
|
||||
Assert-SequentialThinking
|
||||
# OpenCode reads from ~/.config/opencode/AGENTS.md
|
||||
Ensure-RuntimeConfig (Join-Path $env:USERPROFILE ".config\opencode\AGENTS.md")
|
||||
Ensure-RuntimeConfig -Runtime "opencode" -Dst (Join-Path $env:USERPROFILE ".config\opencode\AGENTS.md")
|
||||
Write-Host "[mosaic] Launching OpenCode..."
|
||||
& opencode @remaining
|
||||
}
|
||||
@@ -122,11 +249,18 @@ switch ($command) {
|
||||
Assert-AgentsMd
|
||||
Assert-Soul
|
||||
Assert-Runtime "codex"
|
||||
Assert-SequentialThinking
|
||||
# Codex reads from ~/.codex/instructions.md
|
||||
Ensure-RuntimeConfig (Join-Path $env:USERPROFILE ".codex\instructions.md")
|
||||
Ensure-RuntimeConfig -Runtime "codex" -Dst (Join-Path $env:USERPROFILE ".codex\instructions.md")
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user