chore: planning gate — milestones, issues, and task breakdown
Break PRD into 8 milestones (Phase 0–7) with 59 issues on Gitea. Populate TASKS.md, update mission manifest, initialize scratchpad. Repo created at git.mosaicstack.dev/mosaic/mosaic-stack. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
29
scripts/agent/common.sh
Executable file
29
scripts/agent/common.sh
Executable file
@@ -0,0 +1,29 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
repo_root() {
|
||||
git rev-parse --show-toplevel 2>/dev/null || pwd
|
||||
}
|
||||
|
||||
ensure_repo_root() {
|
||||
cd "$(repo_root)"
|
||||
}
|
||||
|
||||
has_remote() {
|
||||
git remote get-url origin >/dev/null 2>&1
|
||||
}
|
||||
|
||||
run_step() {
|
||||
local label="$1"
|
||||
shift
|
||||
echo "[agent-framework] $label"
|
||||
"$@"
|
||||
}
|
||||
|
||||
load_repo_hooks() {
|
||||
local hooks_file=".mosaic/repo-hooks.sh"
|
||||
if [[ -f "$hooks_file" ]]; then
|
||||
# shellcheck disable=SC1090
|
||||
source "$hooks_file"
|
||||
fi
|
||||
}
|
||||
16
scripts/agent/critical.sh
Executable file
16
scripts/agent/critical.sh
Executable file
@@ -0,0 +1,16 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
# shellcheck source=./common.sh
|
||||
source "$SCRIPT_DIR/common.sh"
|
||||
|
||||
ensure_repo_root
|
||||
load_repo_hooks
|
||||
|
||||
if declare -F mosaic_hook_critical >/dev/null 2>&1; then
|
||||
run_step "Run repo critical hook" mosaic_hook_critical
|
||||
else
|
||||
echo "[agent-framework] No repo critical hook configured (.mosaic/repo-hooks.sh)"
|
||||
echo "[agent-framework] Define mosaic_hook_critical() for project-specific priority scans"
|
||||
fi
|
||||
44
scripts/agent/log-limitation.sh
Executable file
44
scripts/agent/log-limitation.sh
Executable file
@@ -0,0 +1,44 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
TITLE="${1:-}"
|
||||
if [[ -z "$TITLE" ]]; then
|
||||
echo "Usage: $0 \"Short limitation title\"" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
FILE="EVOLUTION.md"
|
||||
if [[ ! -f "$FILE" ]]; then
|
||||
echo "[agent-framework] $FILE not found. Create project-specific limitations log if needed."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if command -v rg >/dev/null 2>&1; then
|
||||
last_num=$(rg -o "^### L-[0-9]{3}" "$FILE" | sed 's/^### L-//' | sort -n | tail -1)
|
||||
else
|
||||
last_num=$(grep -E "^### L-[0-9]{3}" "$FILE" | sed 's/^### L-//' | sort -n | tail -1)
|
||||
fi
|
||||
|
||||
if [[ -z "$last_num" ]]; then
|
||||
next_num="001"
|
||||
else
|
||||
next_num=$(printf "%03d" $((10#$last_num + 1)))
|
||||
fi
|
||||
|
||||
entry_id="L-$next_num"
|
||||
|
||||
cat <<EOF2
|
||||
|
||||
### $entry_id: $TITLE
|
||||
|
||||
| Aspect | Details |
|
||||
|--------|---------|
|
||||
| **Pain** | TODO |
|
||||
| **Impact** | TODO |
|
||||
| **Frequency** | TODO |
|
||||
| **Current Workaround** | TODO |
|
||||
| **Proposed Solution** | TODO |
|
||||
| **Platform Implication** | TODO |
|
||||
EOF2
|
||||
|
||||
echo "[agent-framework] Suggested limitation ID: $entry_id"
|
||||
102
scripts/agent/orchestrator-daemon.sh
Executable file
102
scripts/agent/orchestrator-daemon.sh
Executable file
@@ -0,0 +1,102 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
# shellcheck source=./common.sh
|
||||
source "$SCRIPT_DIR/common.sh"
|
||||
|
||||
ensure_repo_root
|
||||
|
||||
MOSAIC_HOME="${MOSAIC_HOME:-$HOME/.config/mosaic}"
|
||||
ORCH_DIR=".mosaic/orchestrator"
|
||||
PID_FILE="$ORCH_DIR/orchestrator.pid"
|
||||
LOG_FILE="$ORCH_DIR/logs/daemon.log"
|
||||
|
||||
usage() {
|
||||
cat <<USAGE
|
||||
Usage: $(basename "$0") <start|drain|stop|status> [--poll-sec N] [--no-sync]
|
||||
|
||||
Commands:
|
||||
start Run orchestrator drain loop in background (detached)
|
||||
drain Run orchestrator drain loop in foreground (until queue drained)
|
||||
stop Stop background orchestrator if running
|
||||
status Show background orchestrator status
|
||||
|
||||
Options:
|
||||
--poll-sec N Poll interval (default: 15)
|
||||
--no-sync Skip docs/TASKS.md -> orchestrator queue sync before run
|
||||
USAGE
|
||||
}
|
||||
|
||||
cmd="${1:-status}"
|
||||
if [[ $# -gt 0 ]]; then
|
||||
shift
|
||||
fi
|
||||
|
||||
poll_sec=15
|
||||
sync_arg=""
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--poll-sec)
|
||||
poll_sec="${2:-15}"
|
||||
shift 2
|
||||
;;
|
||||
--no-sync)
|
||||
sync_arg="--no-sync"
|
||||
shift
|
||||
;;
|
||||
*)
|
||||
echo "[agent-framework] unknown argument: $1" >&2
|
||||
usage
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
mkdir -p "$ORCH_DIR/logs" "$ORCH_DIR/results"
|
||||
|
||||
is_running() {
|
||||
[[ -f "$PID_FILE" ]] || return 1
|
||||
local pid
|
||||
pid="$(cat "$PID_FILE" 2>/dev/null || true)"
|
||||
[[ -n "$pid" ]] || return 1
|
||||
kill -0 "$pid" 2>/dev/null
|
||||
}
|
||||
|
||||
case "$cmd" in
|
||||
start)
|
||||
if is_running; then
|
||||
echo "[agent-framework] orchestrator already running (pid=$(cat "$PID_FILE"))"
|
||||
exit 0
|
||||
fi
|
||||
nohup "$MOSAIC_HOME/bin/mosaic-orchestrator-drain" --poll-sec "$poll_sec" $sync_arg >"$LOG_FILE" 2>&1 &
|
||||
echo "$!" > "$PID_FILE"
|
||||
echo "[agent-framework] orchestrator started (pid=$!, log=$LOG_FILE)"
|
||||
;;
|
||||
drain)
|
||||
exec "$MOSAIC_HOME/bin/mosaic-orchestrator-drain" --poll-sec "$poll_sec" $sync_arg
|
||||
;;
|
||||
stop)
|
||||
if ! is_running; then
|
||||
echo "[agent-framework] orchestrator not running"
|
||||
rm -f "$PID_FILE"
|
||||
exit 0
|
||||
fi
|
||||
pid="$(cat "$PID_FILE")"
|
||||
kill "$pid" || true
|
||||
rm -f "$PID_FILE"
|
||||
echo "[agent-framework] orchestrator stopped (pid=$pid)"
|
||||
;;
|
||||
status)
|
||||
if is_running; then
|
||||
echo "[agent-framework] orchestrator running (pid=$(cat "$PID_FILE"), log=$LOG_FILE)"
|
||||
else
|
||||
echo "[agent-framework] orchestrator not running"
|
||||
rm -f "$PID_FILE"
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
usage
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
63
scripts/agent/orchestrator-worker.sh
Executable file
63
scripts/agent/orchestrator-worker.sh
Executable file
@@ -0,0 +1,63 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
task_file="${1:-}"
|
||||
if [[ -z "$task_file" || ! -f "$task_file" ]]; then
|
||||
echo "[orchestrator-worker] missing task file argument" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
worker_exec="${MOSAIC_WORKER_EXEC:-}"
|
||||
if [[ -z "$worker_exec" ]]; then
|
||||
if command -v codex >/dev/null 2>&1; then
|
||||
worker_exec="codex -p"
|
||||
elif command -v opencode >/dev/null 2>&1; then
|
||||
worker_exec="opencode -p"
|
||||
else
|
||||
echo "[orchestrator-worker] set MOSAIC_WORKER_EXEC to your worker command (example: 'codex -p' or 'opencode -p')" >&2
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
prompt="$(python3 - "$task_file" <<'PY'
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
task = json.loads(Path(sys.argv[1]).read_text(encoding="utf-8"))
|
||||
task_id = str(task.get("id", "TASK"))
|
||||
title = str(task.get("title", ""))
|
||||
description = str(task.get("description", ""))
|
||||
meta = task.get("metadata", {}) or {}
|
||||
issue = str(meta.get("issue", ""))
|
||||
repo = str(meta.get("repo", ""))
|
||||
branch = str(meta.get("branch", ""))
|
||||
depends = task.get("depends_on", [])
|
||||
if isinstance(depends, list):
|
||||
depends_str = ", ".join(str(x) for x in depends)
|
||||
else:
|
||||
depends_str = str(depends)
|
||||
|
||||
print(
|
||||
f"""Read ~/.config/mosaic/STANDARDS.md, then AGENTS.md and SOUL.md (if present).
|
||||
Complete this queued task fully.
|
||||
|
||||
Task ID: {task_id}
|
||||
Title: {title}
|
||||
Description: {description}
|
||||
Issue: {issue}
|
||||
Repo hint: {repo}
|
||||
Branch hint: {branch}
|
||||
Depends on: {depends_str}
|
||||
|
||||
Requirements:
|
||||
- Implement and verify the task end-to-end.
|
||||
- Keep changes scoped to this task.
|
||||
- Run project checks and tests relevant to touched code.
|
||||
- Return with a concise summary of what changed and verification results.
|
||||
"""
|
||||
)
|
||||
PY
|
||||
)"
|
||||
|
||||
PROMPT="$prompt" bash -lc "$worker_exec \"\$PROMPT\""
|
||||
48
scripts/agent/session-end.sh
Executable file
48
scripts/agent/session-end.sh
Executable file
@@ -0,0 +1,48 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
# shellcheck source=./common.sh
|
||||
source "$SCRIPT_DIR/common.sh"
|
||||
|
||||
ensure_repo_root
|
||||
load_repo_hooks
|
||||
|
||||
# ─── Mission session cleanup (ORCHESTRATOR-PROTOCOL) ────────────────────────
|
||||
ORCH_DIR=".mosaic/orchestrator"
|
||||
MISSION_JSON="$ORCH_DIR/mission.json"
|
||||
SESSION_LOCK="$ORCH_DIR/session.lock"
|
||||
COORD_LIB="$HOME/.config/mosaic/tools/orchestrator/_lib.sh"
|
||||
|
||||
if [[ -f "$SESSION_LOCK" ]] && [[ -f "$COORD_LIB" ]] && command -v jq &>/dev/null; then
|
||||
# shellcheck source=/dev/null
|
||||
source "$COORD_LIB"
|
||||
|
||||
sess_id="$(jq -r '.session_id // ""' "$SESSION_LOCK")"
|
||||
if [[ -n "$sess_id" && -f "$MISSION_JSON" ]]; then
|
||||
# Update mission.json: mark session ended
|
||||
updated="$(jq \
|
||||
--arg sid "$sess_id" \
|
||||
--arg ts "$(iso_now)" \
|
||||
--arg reason "completed" \
|
||||
'(.sessions[] | select(.session_id == $sid)) |= . + {
|
||||
ended_at: $ts,
|
||||
ended_reason: $reason
|
||||
}' "$MISSION_JSON")"
|
||||
echo "$updated" > "$MISSION_JSON.tmp" && mv "$MISSION_JSON.tmp" "$MISSION_JSON"
|
||||
echo "[agent-framework] Session $sess_id recorded in mission state"
|
||||
fi
|
||||
|
||||
session_lock_clear "."
|
||||
fi
|
||||
|
||||
if declare -F mosaic_hook_session_end >/dev/null 2>&1; then
|
||||
run_step "Run repo end hook" mosaic_hook_session_end
|
||||
else
|
||||
echo "[agent-framework] No repo end hook configured (.mosaic/repo-hooks.sh)"
|
||||
fi
|
||||
|
||||
if git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
|
||||
run_step "Show status" git status --short
|
||||
run_step "Show diff summary" git diff --stat
|
||||
fi
|
||||
92
scripts/agent/session-start.sh
Executable file
92
scripts/agent/session-start.sh
Executable file
@@ -0,0 +1,92 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
# shellcheck source=./common.sh
|
||||
source "$SCRIPT_DIR/common.sh"
|
||||
|
||||
ensure_repo_root
|
||||
load_repo_hooks
|
||||
|
||||
if git rev-parse --is-inside-work-tree >/dev/null 2>&1 && has_remote; then
|
||||
if git diff --quiet && git diff --cached --quiet; then
|
||||
run_step "Pull latest changes" git pull --rebase
|
||||
else
|
||||
echo "[agent-framework] Skip pull: working tree has local changes"
|
||||
fi
|
||||
fi
|
||||
|
||||
# ─── Mission state detection (ORCHESTRATOR-PROTOCOL) ────────────────────────
|
||||
ORCH_DIR=".mosaic/orchestrator"
|
||||
MISSION_JSON="$ORCH_DIR/mission.json"
|
||||
COORD_LIB="$HOME/.config/mosaic/tools/orchestrator/_lib.sh"
|
||||
|
||||
if [[ -f "$MISSION_JSON" ]] && command -v jq &>/dev/null; then
|
||||
mission_status="$(jq -r '.status // "inactive"' "$MISSION_JSON")"
|
||||
|
||||
if [[ "$mission_status" == "active" || "$mission_status" == "paused" ]]; then
|
||||
mission_name="$(jq -r '.name // "unnamed"' "$MISSION_JSON")"
|
||||
echo ""
|
||||
echo "========================================="
|
||||
echo "ACTIVE MISSION DETECTED"
|
||||
echo "========================================="
|
||||
echo " Mission: $mission_name"
|
||||
|
||||
# Extract key fields from manifest if present
|
||||
manifest="docs/MISSION-MANIFEST.md"
|
||||
if [[ -f "$manifest" ]]; then
|
||||
phase="$(grep -m1 '^\*\*Phase:\*\*' "$manifest" 2>/dev/null | sed 's/.*\*\*Phase:\*\* //' || true)"
|
||||
milestone="$(grep -m1 '^\*\*Current Milestone:\*\*' "$manifest" 2>/dev/null | sed 's/.*\*\*Current Milestone:\*\* //' || true)"
|
||||
progress="$(grep -m1 '^\*\*Progress:\*\*' "$manifest" 2>/dev/null | sed 's/.*\*\*Progress:\*\* //' || true)"
|
||||
[[ -n "$phase" ]] && echo " Phase: $phase"
|
||||
[[ -n "$milestone" ]] && echo " Milestone: $milestone"
|
||||
[[ -n "$progress" ]] && echo " Progress: $progress"
|
||||
fi
|
||||
|
||||
# Task counts
|
||||
if [[ -f "docs/TASKS.md" ]]; then
|
||||
total="$(grep -c '^|' "docs/TASKS.md" 2>/dev/null || true)"
|
||||
total="${total:-0}"
|
||||
done_count="$(grep -ci '| done \|| completed ' "docs/TASKS.md" 2>/dev/null || true)"
|
||||
done_count="${done_count:-0}"
|
||||
approx_total=$(( total > 2 ? total - 2 : 0 ))
|
||||
echo " Tasks: ~${done_count} done of ~${approx_total} total"
|
||||
fi
|
||||
|
||||
# Scratchpad
|
||||
if [[ -d "docs/scratchpads" ]]; then
|
||||
latest_sp="$(ls -t docs/scratchpads/*.md 2>/dev/null | head -1 || true)"
|
||||
[[ -n "$latest_sp" ]] && echo " Scratchpad: $latest_sp"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo " Resume: Read manifest + scratchpad before taking action."
|
||||
echo " Protocol: ~/.config/mosaic/guides/ORCHESTRATOR-PROTOCOL.md"
|
||||
echo "========================================="
|
||||
echo ""
|
||||
|
||||
# Register session if coordinator lib is available
|
||||
if [[ -f "$COORD_LIB" ]]; then
|
||||
# shellcheck source=/dev/null
|
||||
source "$COORD_LIB"
|
||||
sess_id="$(next_session_id ".")"
|
||||
runtime="${MOSAIC_RUNTIME:-unknown}"
|
||||
session_lock_write "." "$sess_id" "$runtime" "$$"
|
||||
|
||||
# Append session to mission.json
|
||||
updated="$(jq \
|
||||
--arg sid "$sess_id" \
|
||||
--arg rt "$runtime" \
|
||||
--arg ts "$(iso_now)" \
|
||||
'.sessions += [{"session_id":$sid,"runtime":$rt,"started_at":$ts,"ended_at":"","ended_reason":"","milestone_at_end":"","tasks_completed":[],"last_task_id":""}]' \
|
||||
"$MISSION_JSON")"
|
||||
echo "$updated" > "$MISSION_JSON.tmp" && mv "$MISSION_JSON.tmp" "$MISSION_JSON"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
if declare -F mosaic_hook_session_start >/dev/null 2>&1; then
|
||||
run_step "Run repo start hook" mosaic_hook_session_start
|
||||
else
|
||||
echo "[agent-framework] No repo start hook configured (.mosaic/repo-hooks.sh)"
|
||||
fi
|
||||
Reference in New Issue
Block a user