feat: integrate framework files into monorepo under packages/mosaic/framework/
Moves all Mosaic framework runtime files from the separate bootstrap repo into the monorepo as canonical source. The @mosaic/mosaic npm package now ships the complete framework — bin scripts, runtime configs, tools, and templates — enabling standalone installation via npm install. Structure: packages/mosaic/framework/ ├── bin/ 28 CLI scripts (mosaic, mosaic-doctor, mosaic-sync-skills, etc.) ├── runtime/ Runtime adapters (claude, codex, opencode, pi, mcp) ├── tools/ Shell tooling (git, prdy, orchestrator, quality, etc.) ├── templates/ Agent and repo templates ├── defaults/ Default identity files (AGENTS.md, STANDARDS.md, SOUL.md, etc.) ├── install.sh Legacy bash installer └── remote-install.sh One-liner remote installer Key files with Pi support and recent fixes: - bin/mosaic: launch_pi() with skills-local loop - bin/mosaic-doctor: --fix auto-wiring for all 4 harnesses - bin/mosaic-sync-skills: Pi as 4th link target, symlink-aware find - bin/mosaic-link-runtime-assets: Pi settings.json patching - bin/mosaic-migrate-local-skills: Pi skill roots, symlink find - runtime/pi/RUNTIME.md + mosaic-extension.ts Package ships 251 framework files in the npm tarball (278KB compressed).
This commit is contained in:
305
packages/mosaic/framework/tools/bootstrap/agent-lint.sh
Executable file
305
packages/mosaic/framework/tools/bootstrap/agent-lint.sh
Executable file
@@ -0,0 +1,305 @@
|
||||
#!/bin/bash
|
||||
# agent-lint.sh — Audit agent configuration across all coding projects
|
||||
#
|
||||
# Usage:
|
||||
# agent-lint.sh # Scan all projects in ~/src/
|
||||
# agent-lint.sh --project <path> # Scan single project
|
||||
# agent-lint.sh --json # Output JSON for jarvis-brain
|
||||
# agent-lint.sh --verbose # Show per-check details
|
||||
# agent-lint.sh --fix-hint # Show fix commands for failures
|
||||
#
|
||||
# Checks per project:
|
||||
# 1. Has runtime context file (CLAUDE.md or RUNTIME.md)?
|
||||
# 2. Has AGENTS.md?
|
||||
# 3. Runtime context file references conditional context/guides?
|
||||
# 4. Runtime context file has quality gates?
|
||||
# 5. For monorepos: sub-directories have AGENTS.md?
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Defaults
|
||||
SRC_DIR="$HOME/src"
|
||||
SINGLE_PROJECT=""
|
||||
JSON_OUTPUT=false
|
||||
VERBOSE=false
|
||||
FIX_HINT=false
|
||||
|
||||
# Exclusion patterns (not coding projects)
|
||||
EXCLUDE_PATTERNS=(
|
||||
"_worktrees"
|
||||
".backup"
|
||||
"_old"
|
||||
"_bak"
|
||||
"junk"
|
||||
"traefik"
|
||||
"infrastructure"
|
||||
)
|
||||
|
||||
# Parse args
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--project) SINGLE_PROJECT="$2"; shift 2 ;;
|
||||
--json) JSON_OUTPUT=true; shift ;;
|
||||
--verbose) VERBOSE=true; shift ;;
|
||||
--fix-hint) FIX_HINT=true; shift ;;
|
||||
--src-dir) SRC_DIR="$2"; shift 2 ;;
|
||||
-h|--help)
|
||||
echo "Usage: agent-lint.sh [--project <path>] [--json] [--verbose] [--fix-hint] [--src-dir <dir>]"
|
||||
exit 0
|
||||
;;
|
||||
*) echo "Unknown option: $1"; exit 1 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
# Colors (disabled for JSON mode)
|
||||
if $JSON_OUTPUT; then
|
||||
GREEN="" RED="" YELLOW="" NC="" BOLD="" DIM=""
|
||||
else
|
||||
GREEN='\033[0;32m' RED='\033[0;31m' YELLOW='\033[0;33m'
|
||||
NC='\033[0m' BOLD='\033[1m' DIM='\033[2m'
|
||||
fi
|
||||
|
||||
# Determine if a directory is a coding project
|
||||
is_coding_project() {
|
||||
local dir="$1"
|
||||
[[ -f "$dir/package.json" ]] || \
|
||||
[[ -f "$dir/pyproject.toml" ]] || \
|
||||
[[ -f "$dir/Cargo.toml" ]] || \
|
||||
[[ -f "$dir/go.mod" ]] || \
|
||||
[[ -f "$dir/Makefile" && -f "$dir/src/main.rs" ]] || \
|
||||
[[ -f "$dir/pom.xml" ]] || \
|
||||
[[ -f "$dir/build.gradle" ]]
|
||||
}
|
||||
|
||||
# Check if directory should be excluded
|
||||
is_excluded() {
|
||||
local dir_name
|
||||
dir_name=$(basename "$1")
|
||||
for pattern in "${EXCLUDE_PATTERNS[@]}"; do
|
||||
if [[ "$dir_name" == *"$pattern"* ]]; then
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
# Detect if project is a monorepo
|
||||
is_monorepo() {
|
||||
local dir="$1"
|
||||
[[ -f "$dir/pnpm-workspace.yaml" ]] || \
|
||||
[[ -f "$dir/turbo.json" ]] || \
|
||||
[[ -f "$dir/lerna.json" ]] || \
|
||||
(grep -q '"workspaces"' "$dir/package.json" 2>/dev/null)
|
||||
}
|
||||
|
||||
# Resolve runtime context file (CLAUDE.md or RUNTIME.md)
|
||||
runtime_context_file() {
|
||||
local dir="$1"
|
||||
if [[ -f "$dir/CLAUDE.md" ]]; then
|
||||
echo "$dir/CLAUDE.md"
|
||||
return
|
||||
fi
|
||||
if [[ -f "$dir/RUNTIME.md" ]]; then
|
||||
echo "$dir/RUNTIME.md"
|
||||
return
|
||||
fi
|
||||
echo ""
|
||||
}
|
||||
|
||||
# Check for runtime context file
|
||||
check_runtime_context() {
|
||||
[[ -n "$(runtime_context_file "$1")" ]]
|
||||
}
|
||||
|
||||
# Check for AGENTS.md
|
||||
check_agents_md() {
|
||||
[[ -f "$1/AGENTS.md" ]]
|
||||
}
|
||||
|
||||
# Check conditional loading/context (references guides or conditional section)
|
||||
check_conditional_loading() {
|
||||
local ctx
|
||||
ctx="$(runtime_context_file "$1")"
|
||||
[[ -n "$ctx" ]] && grep -qi "agent-guides\|~/.config/mosaic/guides\|conditional.*loading\|conditional.*documentation\|conditional.*context" "$ctx" 2>/dev/null
|
||||
}
|
||||
|
||||
# Check quality gates
|
||||
check_quality_gates() {
|
||||
local ctx
|
||||
ctx="$(runtime_context_file "$1")"
|
||||
[[ -n "$ctx" ]] && grep -qi "quality.gates\|must pass before\|lint\|typecheck\|test" "$ctx" 2>/dev/null
|
||||
}
|
||||
|
||||
# Check monorepo sub-AGENTS.md
|
||||
check_monorepo_sub_agents() {
|
||||
local dir="$1"
|
||||
local missing=()
|
||||
|
||||
if ! is_monorepo "$dir"; then
|
||||
echo "N/A"
|
||||
return
|
||||
fi
|
||||
|
||||
# Check apps/, packages/, services/, plugins/ directories
|
||||
for subdir_type in apps packages services plugins; do
|
||||
if [[ -d "$dir/$subdir_type" ]]; then
|
||||
for subdir in "$dir/$subdir_type"/*/; do
|
||||
[[ -d "$subdir" ]] || continue
|
||||
# Only check if it has its own manifest
|
||||
if [[ -f "$subdir/package.json" ]] || [[ -f "$subdir/pyproject.toml" ]]; then
|
||||
if [[ ! -f "$subdir/AGENTS.md" ]]; then
|
||||
missing+=("$(basename "$subdir")")
|
||||
fi
|
||||
fi
|
||||
done
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ ${#missing[@]} -eq 0 ]]; then
|
||||
echo "OK"
|
||||
else
|
||||
echo "MISS:${missing[*]}"
|
||||
fi
|
||||
}
|
||||
|
||||
# Lint a single project
|
||||
lint_project() {
|
||||
local dir="$1"
|
||||
local name
|
||||
name=$(basename "$dir")
|
||||
|
||||
local has_runtime has_agents has_guides has_quality mono_status
|
||||
local score=0 max_score=4
|
||||
|
||||
check_runtime_context "$dir" && has_runtime="OK" || has_runtime="MISS"
|
||||
check_agents_md "$dir" && has_agents="OK" || has_agents="MISS"
|
||||
check_conditional_loading "$dir" && has_guides="OK" || has_guides="MISS"
|
||||
check_quality_gates "$dir" && has_quality="OK" || has_quality="MISS"
|
||||
mono_status=$(check_monorepo_sub_agents "$dir")
|
||||
|
||||
[[ "$has_runtime" == "OK" ]] && ((score++)) || true
|
||||
[[ "$has_agents" == "OK" ]] && ((score++)) || true
|
||||
[[ "$has_guides" == "OK" ]] && ((score++)) || true
|
||||
[[ "$has_quality" == "OK" ]] && ((score++)) || true
|
||||
|
||||
if $JSON_OUTPUT; then
|
||||
cat <<JSONEOF
|
||||
{
|
||||
"project": "$name",
|
||||
"path": "$dir",
|
||||
"runtime_context": "$has_runtime",
|
||||
"agents_md": "$has_agents",
|
||||
"conditional_loading": "$has_guides",
|
||||
"quality_gates": "$has_quality",
|
||||
"monorepo_sub_agents": "$mono_status",
|
||||
"score": $score,
|
||||
"max_score": $max_score
|
||||
}
|
||||
JSONEOF
|
||||
else
|
||||
# Color-code the status
|
||||
local c_runtime c_agents c_guides c_quality
|
||||
[[ "$has_runtime" == "OK" ]] && c_runtime="${GREEN} OK ${NC}" || c_runtime="${RED} MISS ${NC}"
|
||||
[[ "$has_agents" == "OK" ]] && c_agents="${GREEN} OK ${NC}" || c_agents="${RED} MISS ${NC}"
|
||||
[[ "$has_guides" == "OK" ]] && c_guides="${GREEN} OK ${NC}" || c_guides="${RED} MISS ${NC}"
|
||||
[[ "$has_quality" == "OK" ]] && c_quality="${GREEN} OK ${NC}" || c_quality="${RED} MISS ${NC}"
|
||||
|
||||
local score_color="$RED"
|
||||
[[ $score -ge 3 ]] && score_color="$YELLOW"
|
||||
[[ $score -eq 4 ]] && score_color="$GREEN"
|
||||
|
||||
printf " %-35s %b %b %b %b ${score_color}%d/%d${NC}" \
|
||||
"$name" "$c_runtime" "$c_agents" "$c_guides" "$c_quality" "$score" "$max_score"
|
||||
|
||||
# Show monorepo status if applicable
|
||||
if [[ "$mono_status" != "N/A" && "$mono_status" != "OK" ]]; then
|
||||
printf " ${YELLOW}(mono: %s)${NC}" "$mono_status"
|
||||
fi
|
||||
echo ""
|
||||
fi
|
||||
|
||||
if $VERBOSE && ! $JSON_OUTPUT; then
|
||||
[[ "$has_runtime" == "MISS" ]] && echo " ${DIM} Runtime context file missing (CLAUDE.md or RUNTIME.md)${NC}"
|
||||
[[ "$has_agents" == "MISS" ]] && echo " ${DIM} AGENTS.md missing${NC}"
|
||||
[[ "$has_guides" == "MISS" ]] && echo " ${DIM} No conditional context/loading section detected${NC}"
|
||||
[[ "$has_quality" == "MISS" ]] && echo " ${DIM} No quality gates section${NC}"
|
||||
if [[ "$mono_status" == MISS:* ]]; then
|
||||
echo " ${DIM} Monorepo sub-AGENTS.md missing: ${mono_status#MISS:}${NC}"
|
||||
fi
|
||||
fi
|
||||
|
||||
if $FIX_HINT && ! $JSON_OUTPUT; then
|
||||
if [[ "$has_runtime" == "MISS" || "$has_agents" == "MISS" ]]; then
|
||||
echo " ${DIM}Fix: ~/.config/mosaic/tools/bootstrap/init-project.sh --name \"$name\" --type auto${NC}"
|
||||
elif [[ "$has_guides" == "MISS" ]]; then
|
||||
echo " ${DIM}Fix: ~/.config/mosaic/tools/bootstrap/agent-upgrade.sh $dir --section conditional-loading${NC}"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Return score for summary
|
||||
echo "$score" > /tmp/agent-lint-score-$$
|
||||
}
|
||||
|
||||
# Main
|
||||
main() {
|
||||
local projects=()
|
||||
local total=0 passing=0 total_score=0
|
||||
|
||||
if [[ -n "$SINGLE_PROJECT" ]]; then
|
||||
projects=("$SINGLE_PROJECT")
|
||||
else
|
||||
for dir in "$SRC_DIR"/*/; do
|
||||
[[ -d "$dir" ]] || continue
|
||||
is_excluded "$dir" && continue
|
||||
is_coding_project "$dir" && projects+=("${dir%/}")
|
||||
done
|
||||
fi
|
||||
|
||||
if [[ ${#projects[@]} -eq 0 ]]; then
|
||||
echo "No coding projects found."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if $JSON_OUTPUT; then
|
||||
echo '{ "audit_date": "'$(date -I)'", "projects": ['
|
||||
local first=true
|
||||
for dir in "${projects[@]}"; do
|
||||
$first || echo ","
|
||||
first=false
|
||||
lint_project "$dir"
|
||||
done
|
||||
echo '] }'
|
||||
else
|
||||
echo ""
|
||||
echo -e "${BOLD}Agent Configuration Audit — $(date +%Y-%m-%d)${NC}"
|
||||
echo "========================================================"
|
||||
printf " %-35s %s %s %s %s %s\n" \
|
||||
"Project" "RUNTIME" "AGENTS" "Guides" "Quality" "Score"
|
||||
echo " -----------------------------------------------------------------------"
|
||||
|
||||
for dir in "${projects[@]}"; do
|
||||
lint_project "$dir"
|
||||
local score
|
||||
score=$(cat /tmp/agent-lint-score-$$ 2>/dev/null || echo 0)
|
||||
((total++)) || true
|
||||
((total_score += score)) || true
|
||||
[[ $score -eq 4 ]] && ((passing++)) || true
|
||||
done
|
||||
|
||||
rm -f /tmp/agent-lint-score-$$
|
||||
|
||||
echo " -----------------------------------------------------------------------"
|
||||
local need_attention=$((total - passing))
|
||||
echo ""
|
||||
echo -e " ${BOLD}Summary:${NC} $total projects | ${GREEN}$passing pass${NC} | ${RED}$need_attention need attention${NC}"
|
||||
echo ""
|
||||
|
||||
if [[ $need_attention -gt 0 ]] && ! $FIX_HINT; then
|
||||
echo -e " ${DIM}Run with --fix-hint for suggested fixes${NC}"
|
||||
echo -e " ${DIM}Run with --verbose for per-check details${NC}"
|
||||
echo ""
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
main
|
||||
332
packages/mosaic/framework/tools/bootstrap/agent-upgrade.sh
Executable file
332
packages/mosaic/framework/tools/bootstrap/agent-upgrade.sh
Executable file
@@ -0,0 +1,332 @@
|
||||
#!/bin/bash
|
||||
# agent-upgrade.sh — Non-destructively upgrade agent configuration in projects
|
||||
#
|
||||
# Usage:
|
||||
# agent-upgrade.sh <project-path> # Upgrade one project
|
||||
# agent-upgrade.sh --all # Upgrade all projects in ~/src/
|
||||
# agent-upgrade.sh --all --dry-run # Preview what would change
|
||||
# agent-upgrade.sh <path> --section conditional-loading # Inject specific section
|
||||
# agent-upgrade.sh <path> --create-agents # Create AGENTS.md if missing
|
||||
# agent-upgrade.sh <path> --monorepo-scan # Create sub-AGENTS.md for monorepo dirs
|
||||
#
|
||||
# Safety:
|
||||
# - Creates .bak backup before any modification
|
||||
# - Append-only — never modifies existing sections
|
||||
# - --dry-run shows what would change without writing
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Defaults
|
||||
SRC_DIR="$HOME/src"
|
||||
FRAGMENTS_DIR="$HOME/.config/mosaic/templates/agent/fragments"
|
||||
TEMPLATES_DIR="$HOME/.config/mosaic/templates/agent"
|
||||
DRY_RUN=false
|
||||
ALL_PROJECTS=false
|
||||
TARGET_PATH=""
|
||||
SECTION_ONLY=""
|
||||
CREATE_AGENTS=false
|
||||
MONOREPO_SCAN=false
|
||||
|
||||
# Exclusion patterns (same as agent-lint.sh)
|
||||
EXCLUDE_PATTERNS=(
|
||||
"_worktrees"
|
||||
".backup"
|
||||
"_old"
|
||||
"_bak"
|
||||
"junk"
|
||||
"traefik"
|
||||
"infrastructure"
|
||||
)
|
||||
|
||||
# Colors
|
||||
GREEN='\033[0;32m' RED='\033[0;31m' YELLOW='\033[0;33m'
|
||||
NC='\033[0m' BOLD='\033[1m' DIM='\033[2m'
|
||||
|
||||
# Parse args
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--all) ALL_PROJECTS=true; shift ;;
|
||||
--dry-run) DRY_RUN=true; shift ;;
|
||||
--section) SECTION_ONLY="$2"; shift 2 ;;
|
||||
--create-agents) CREATE_AGENTS=true; shift ;;
|
||||
--monorepo-scan) MONOREPO_SCAN=true; shift ;;
|
||||
--src-dir) SRC_DIR="$2"; shift 2 ;;
|
||||
-h|--help)
|
||||
echo "Usage: agent-upgrade.sh [<project-path>|--all] [--dry-run] [--section <name>] [--create-agents] [--monorepo-scan]"
|
||||
echo ""
|
||||
echo "Options:"
|
||||
echo " --all Upgrade all projects in ~/src/"
|
||||
echo " --dry-run Preview changes without writing"
|
||||
echo " --section <name> Inject only a specific fragment (conditional-loading, commit-format, secrets, multi-agent, code-review, campsite-rule)"
|
||||
echo " --create-agents Create AGENTS.md if missing"
|
||||
echo " --monorepo-scan Create sub-AGENTS.md for monorepo directories"
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
if [[ -d "$1" ]]; then
|
||||
TARGET_PATH="$1"
|
||||
else
|
||||
echo "Unknown option or invalid path: $1"
|
||||
exit 1
|
||||
fi
|
||||
shift
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if ! $ALL_PROJECTS && [[ -z "$TARGET_PATH" ]]; then
|
||||
echo "Error: Specify a project path or use --all"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Helpers
|
||||
is_coding_project() {
|
||||
local dir="$1"
|
||||
[[ -f "$dir/package.json" ]] || \
|
||||
[[ -f "$dir/pyproject.toml" ]] || \
|
||||
[[ -f "$dir/Cargo.toml" ]] || \
|
||||
[[ -f "$dir/go.mod" ]] || \
|
||||
[[ -f "$dir/pom.xml" ]] || \
|
||||
[[ -f "$dir/build.gradle" ]]
|
||||
}
|
||||
|
||||
is_excluded() {
|
||||
local dir_name
|
||||
dir_name=$(basename "$1")
|
||||
for pattern in "${EXCLUDE_PATTERNS[@]}"; do
|
||||
[[ "$dir_name" == *"$pattern"* ]] && return 0
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
is_monorepo() {
|
||||
local dir="$1"
|
||||
[[ -f "$dir/pnpm-workspace.yaml" ]] || \
|
||||
[[ -f "$dir/turbo.json" ]] || \
|
||||
[[ -f "$dir/lerna.json" ]] || \
|
||||
(grep -q '"workspaces"' "$dir/package.json" 2>/dev/null)
|
||||
}
|
||||
|
||||
has_section() {
|
||||
local file="$1"
|
||||
local pattern="$2"
|
||||
[[ -f "$file" ]] && grep -qi "$pattern" "$file" 2>/dev/null
|
||||
}
|
||||
|
||||
runtime_context_file() {
|
||||
local project_dir="$1"
|
||||
if [[ -f "$project_dir/CLAUDE.md" ]]; then
|
||||
echo "$project_dir/CLAUDE.md"
|
||||
return
|
||||
fi
|
||||
if [[ -f "$project_dir/RUNTIME.md" ]]; then
|
||||
echo "$project_dir/RUNTIME.md"
|
||||
return
|
||||
fi
|
||||
echo "$project_dir/CLAUDE.md"
|
||||
}
|
||||
|
||||
backup_file() {
|
||||
local file="$1"
|
||||
if [[ -f "$file" ]] && ! $DRY_RUN; then
|
||||
cp "$file" "${file}.bak"
|
||||
fi
|
||||
}
|
||||
|
||||
# Inject a fragment into CLAUDE.md if the section doesn't exist
|
||||
inject_fragment() {
|
||||
local project_dir="$1"
|
||||
local fragment_name="$2"
|
||||
local ctx_file
|
||||
ctx_file="$(runtime_context_file "$project_dir")"
|
||||
local fragment_file="$FRAGMENTS_DIR/$fragment_name.md"
|
||||
|
||||
if [[ ! -f "$fragment_file" ]]; then
|
||||
echo -e " ${RED}Fragment not found: $fragment_file${NC}"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Determine detection pattern for this fragment
|
||||
local detect_pattern
|
||||
case "$fragment_name" in
|
||||
conditional-loading) detect_pattern="agent-guides\|~/.config/mosaic/guides\|Conditional.*Loading\|Conditional.*Documentation\|Conditional.*Context" ;;
|
||||
commit-format) detect_pattern="<type>.*#issue\|Types:.*feat.*fix" ;;
|
||||
secrets) detect_pattern="NEVER hardcode secrets\|\.env.example.*committed" ;;
|
||||
multi-agent) detect_pattern="Multi-Agent Coordination\|pull --rebase.*before" ;;
|
||||
code-review) detect_pattern="codex-code-review\|codex-security-review\|Code Review" ;;
|
||||
campsite-rule) detect_pattern="Campsite Rule\|Touching it makes it yours\|was already there.*NEVER" ;;
|
||||
*) echo "Unknown fragment: $fragment_name"; return 1 ;;
|
||||
esac
|
||||
|
||||
if [[ ! -f "$ctx_file" ]]; then
|
||||
echo -e " ${YELLOW}No runtime context file (CLAUDE.md/RUNTIME.md) — skipping fragment injection${NC}"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if has_section "$ctx_file" "$detect_pattern"; then
|
||||
echo -e " ${DIM}$fragment_name already present${NC}"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if $DRY_RUN; then
|
||||
echo -e " ${GREEN}Would inject: $fragment_name${NC}"
|
||||
else
|
||||
backup_file "$ctx_file"
|
||||
echo "" >> "$ctx_file"
|
||||
cat "$fragment_file" >> "$ctx_file"
|
||||
echo "" >> "$ctx_file"
|
||||
echo -e " ${GREEN}Injected: $fragment_name${NC}"
|
||||
fi
|
||||
}
|
||||
|
||||
# Create AGENTS.md from template
|
||||
create_agents_md() {
|
||||
local project_dir="$1"
|
||||
local agents_md="$project_dir/AGENTS.md"
|
||||
|
||||
if [[ -f "$agents_md" ]]; then
|
||||
echo -e " ${DIM}AGENTS.md already exists${NC}"
|
||||
return 0
|
||||
fi
|
||||
|
||||
local project_name
|
||||
project_name=$(basename "$project_dir")
|
||||
|
||||
# Detect project type for quality gates
|
||||
local quality_gates="# Add quality gate commands here"
|
||||
if [[ -f "$project_dir/package.json" ]]; then
|
||||
quality_gates="npm run lint && npm run typecheck && npm test"
|
||||
if grep -q '"pnpm"' "$project_dir/package.json" 2>/dev/null || [[ -f "$project_dir/pnpm-lock.yaml" ]]; then
|
||||
quality_gates="pnpm lint && pnpm typecheck && pnpm test"
|
||||
fi
|
||||
elif [[ -f "$project_dir/pyproject.toml" ]]; then
|
||||
quality_gates="uv run ruff check src/ tests/ && uv run mypy src/ && uv run pytest --cov"
|
||||
fi
|
||||
|
||||
if $DRY_RUN; then
|
||||
echo -e " ${GREEN}Would create: AGENTS.md${NC}"
|
||||
else
|
||||
# Use generic AGENTS.md template with substitutions
|
||||
sed -e "s/\${PROJECT_NAME}/$project_name/g" \
|
||||
-e "s/\${QUALITY_GATES}/$quality_gates/g" \
|
||||
-e "s/\${TASK_PREFIX}/${project_name^^}/g" \
|
||||
-e "s|\${SOURCE_DIR}|src|g" \
|
||||
"$TEMPLATES_DIR/AGENTS.md.template" > "$agents_md"
|
||||
echo -e " ${GREEN}Created: AGENTS.md${NC}"
|
||||
fi
|
||||
}
|
||||
|
||||
# Create sub-AGENTS.md for monorepo directories
|
||||
create_sub_agents() {
|
||||
local project_dir="$1"
|
||||
|
||||
if ! is_monorepo "$project_dir"; then
|
||||
echo -e " ${DIM}Not a monorepo — skipping sub-AGENTS scan${NC}"
|
||||
return 0
|
||||
fi
|
||||
|
||||
local created=0
|
||||
for subdir_type in apps packages services plugins; do
|
||||
if [[ -d "$project_dir/$subdir_type" ]]; then
|
||||
for subdir in "$project_dir/$subdir_type"/*/; do
|
||||
[[ -d "$subdir" ]] || continue
|
||||
# Only if it has its own manifest
|
||||
if [[ -f "$subdir/package.json" ]] || [[ -f "$subdir/pyproject.toml" ]]; then
|
||||
if [[ ! -f "$subdir/AGENTS.md" ]]; then
|
||||
local dir_name
|
||||
dir_name=$(basename "$subdir")
|
||||
if $DRY_RUN; then
|
||||
echo -e " ${GREEN}Would create: $subdir_type/$dir_name/AGENTS.md${NC}"
|
||||
else
|
||||
sed -e "s/\${DIRECTORY_NAME}/$dir_name/g" \
|
||||
-e "s/\${DIRECTORY_PURPOSE}/Part of the $subdir_type layer./g" \
|
||||
"$TEMPLATES_DIR/sub-agents.md.template" > "${subdir}AGENTS.md"
|
||||
echo -e " ${GREEN}Created: $subdir_type/$dir_name/AGENTS.md${NC}"
|
||||
fi
|
||||
((created++)) || true
|
||||
fi
|
||||
fi
|
||||
done
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ $created -eq 0 ]]; then
|
||||
echo -e " ${DIM}All monorepo sub-AGENTS.md present${NC}"
|
||||
fi
|
||||
}
|
||||
|
||||
# Upgrade a single project
|
||||
upgrade_project() {
|
||||
local dir="$1"
|
||||
local name
|
||||
name=$(basename "$dir")
|
||||
|
||||
echo -e "\n${BOLD}$name${NC} ${DIM}($dir)${NC}"
|
||||
|
||||
if [[ -n "$SECTION_ONLY" ]]; then
|
||||
inject_fragment "$dir" "$SECTION_ONLY"
|
||||
return
|
||||
fi
|
||||
|
||||
# Always try conditional-loading (highest impact)
|
||||
inject_fragment "$dir" "conditional-loading"
|
||||
|
||||
# Try other fragments if runtime context exists
|
||||
if [[ -f "$dir/CLAUDE.md" || -f "$dir/RUNTIME.md" ]]; then
|
||||
inject_fragment "$dir" "commit-format"
|
||||
inject_fragment "$dir" "secrets"
|
||||
inject_fragment "$dir" "multi-agent"
|
||||
inject_fragment "$dir" "code-review"
|
||||
inject_fragment "$dir" "campsite-rule"
|
||||
fi
|
||||
|
||||
# Create AGENTS.md if missing (always unless --section was used)
|
||||
if $CREATE_AGENTS || [[ -z "$SECTION_ONLY" ]]; then
|
||||
create_agents_md "$dir"
|
||||
fi
|
||||
|
||||
# Monorepo sub-AGENTS.md
|
||||
if $MONOREPO_SCAN || [[ -z "$SECTION_ONLY" ]]; then
|
||||
create_sub_agents "$dir"
|
||||
fi
|
||||
}
|
||||
|
||||
# Main
|
||||
main() {
|
||||
local projects=()
|
||||
|
||||
if $ALL_PROJECTS; then
|
||||
for dir in "$SRC_DIR"/*/; do
|
||||
[[ -d "$dir" ]] || continue
|
||||
is_excluded "$dir" && continue
|
||||
is_coding_project "$dir" && projects+=("${dir%/}")
|
||||
done
|
||||
else
|
||||
projects=("$TARGET_PATH")
|
||||
fi
|
||||
|
||||
if [[ ${#projects[@]} -eq 0 ]]; then
|
||||
echo "No coding projects found."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
local mode="LIVE"
|
||||
$DRY_RUN && mode="DRY RUN"
|
||||
|
||||
echo -e "${BOLD}Agent Configuration Upgrade — $(date +%Y-%m-%d) [$mode]${NC}"
|
||||
echo "========================================================"
|
||||
|
||||
for dir in "${projects[@]}"; do
|
||||
upgrade_project "$dir"
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo -e "${BOLD}Done.${NC}"
|
||||
if $DRY_RUN; then
|
||||
echo -e "${DIM}Run without --dry-run to apply changes.${NC}"
|
||||
else
|
||||
echo -e "${DIM}Backups saved as .bak files. Run agent-lint.sh to verify.${NC}"
|
||||
fi
|
||||
}
|
||||
|
||||
main
|
||||
493
packages/mosaic/framework/tools/bootstrap/init-project.sh
Executable file
493
packages/mosaic/framework/tools/bootstrap/init-project.sh
Executable file
@@ -0,0 +1,493 @@
|
||||
#!/bin/bash
|
||||
# init-project.sh - Bootstrap a project for AI-assisted development
|
||||
# Usage: init-project.sh [OPTIONS]
|
||||
#
|
||||
# Creates CLAUDE.md, AGENTS.md, and standard directories using templates.
|
||||
# Optionally initializes git labels and milestones.
|
||||
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
TEMPLATE_DIR="$HOME/.config/mosaic/templates/agent"
|
||||
GIT_SCRIPT_DIR="$HOME/.config/mosaic/tools/git"
|
||||
SEQUENTIAL_MCP_SCRIPT="$HOME/.config/mosaic/bin/mosaic-ensure-sequential-thinking"
|
||||
|
||||
# Defaults
|
||||
PROJECT_NAME=""
|
||||
PROJECT_TYPE=""
|
||||
REPO_URL=""
|
||||
TASK_PREFIX=""
|
||||
PROJECT_DESCRIPTION=""
|
||||
SKIP_LABELS=false
|
||||
SKIP_CI=false
|
||||
CICD_DOCKER=false
|
||||
DRY_RUN=false
|
||||
declare -a CICD_SERVICES=()
|
||||
CICD_BRANCHES="main,develop"
|
||||
|
||||
show_help() {
|
||||
cat <<'EOF'
|
||||
Usage: init-project.sh [OPTIONS]
|
||||
|
||||
Bootstrap a project for AI-assisted development.
|
||||
|
||||
Options:
|
||||
-n, --name <name> Project name (required)
|
||||
-t, --type <type> Project type: nestjs-nextjs, django, generic (default: auto-detect)
|
||||
-r, --repo <url> Git remote URL
|
||||
-p, --prefix <prefix> Orchestrator task prefix (e.g., MS, UC)
|
||||
-d, --description <desc> One-line project description
|
||||
--skip-labels Skip creating git labels and milestones
|
||||
--skip-ci Skip copying CI pipeline files
|
||||
--cicd-docker Generate Docker build/push/link pipeline steps
|
||||
--cicd-service <name:path> Service for Docker CI (repeatable, requires --cicd-docker)
|
||||
--cicd-branches <list> Branches for Docker builds (default: main,develop)
|
||||
--dry-run Show what would be created without creating anything
|
||||
-h, --help Show this help
|
||||
|
||||
Examples:
|
||||
# Full bootstrap with auto-detection
|
||||
init-project.sh --name "My App" --description "A web application"
|
||||
|
||||
# Specific type
|
||||
init-project.sh --name "My API" --type django --prefix MA
|
||||
|
||||
# Dry run
|
||||
init-project.sh --name "Test" --type generic --dry-run
|
||||
|
||||
# With Docker CI/CD pipeline
|
||||
init-project.sh --name "My App" --cicd-docker \
|
||||
--cicd-service "my-api:src/api/Dockerfile" \
|
||||
--cicd-service "my-web:src/web/Dockerfile"
|
||||
|
||||
Project Types:
|
||||
nestjs-nextjs NestJS + Next.js monorepo (pnpm + TurboRepo)
|
||||
django Django project (pytest + ruff + mypy)
|
||||
typescript Standalone TypeScript/Next.js project
|
||||
python-fastapi Python FastAPI project (pytest + ruff + mypy + uv)
|
||||
python-library Python library/SDK (pytest + ruff + mypy + uv)
|
||||
generic Generic project (uses base templates)
|
||||
auto Auto-detect from project files (default)
|
||||
EOF
|
||||
exit 0
|
||||
}
|
||||
|
||||
# Parse arguments
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case $1 in
|
||||
-n|--name)
|
||||
PROJECT_NAME="$2"
|
||||
shift 2
|
||||
;;
|
||||
-t|--type)
|
||||
PROJECT_TYPE="$2"
|
||||
shift 2
|
||||
;;
|
||||
-r|--repo)
|
||||
REPO_URL="$2"
|
||||
shift 2
|
||||
;;
|
||||
-p|--prefix)
|
||||
TASK_PREFIX="$2"
|
||||
shift 2
|
||||
;;
|
||||
-d|--description)
|
||||
PROJECT_DESCRIPTION="$2"
|
||||
shift 2
|
||||
;;
|
||||
--skip-labels)
|
||||
SKIP_LABELS=true
|
||||
shift
|
||||
;;
|
||||
--skip-ci)
|
||||
SKIP_CI=true
|
||||
shift
|
||||
;;
|
||||
--cicd-docker)
|
||||
CICD_DOCKER=true
|
||||
shift
|
||||
;;
|
||||
--cicd-service)
|
||||
CICD_SERVICES+=("$2")
|
||||
shift 2
|
||||
;;
|
||||
--cicd-branches)
|
||||
CICD_BRANCHES="$2"
|
||||
shift 2
|
||||
;;
|
||||
--dry-run)
|
||||
DRY_RUN=true
|
||||
shift
|
||||
;;
|
||||
-h|--help)
|
||||
show_help
|
||||
;;
|
||||
*)
|
||||
echo "Unknown option: $1" >&2
|
||||
echo "Run with --help for usage" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# Validate required args
|
||||
if [[ -z "$PROJECT_NAME" ]]; then
|
||||
echo "Error: --name is required" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Auto-detect project type if not specified
|
||||
detect_project_type() {
|
||||
# Monorepo (pnpm + turbo or npm workspaces with NestJS)
|
||||
if [[ -f "pnpm-workspace.yaml" ]] || [[ -f "turbo.json" ]]; then
|
||||
echo "nestjs-nextjs"
|
||||
return
|
||||
fi
|
||||
if [[ -f "package.json" ]] && grep -q '"workspaces"' package.json 2>/dev/null; then
|
||||
echo "nestjs-nextjs"
|
||||
return
|
||||
fi
|
||||
# Django
|
||||
if [[ -f "manage.py" ]] && [[ -f "pyproject.toml" ]]; then
|
||||
echo "django"
|
||||
return
|
||||
fi
|
||||
# FastAPI
|
||||
if [[ -f "pyproject.toml" ]] && grep -q "fastapi" pyproject.toml 2>/dev/null; then
|
||||
echo "python-fastapi"
|
||||
return
|
||||
fi
|
||||
# Standalone TypeScript
|
||||
if [[ -f "tsconfig.json" ]] && [[ -f "package.json" ]]; then
|
||||
echo "typescript"
|
||||
return
|
||||
fi
|
||||
# Python library/tool
|
||||
if [[ -f "pyproject.toml" ]]; then
|
||||
echo "python-library"
|
||||
return
|
||||
fi
|
||||
echo "generic"
|
||||
}
|
||||
|
||||
if [[ -z "$PROJECT_TYPE" || "$PROJECT_TYPE" == "auto" ]]; then
|
||||
PROJECT_TYPE=$(detect_project_type)
|
||||
echo "Auto-detected project type: $PROJECT_TYPE"
|
||||
fi
|
||||
|
||||
# Derive defaults
|
||||
if [[ -z "$REPO_URL" ]]; then
|
||||
REPO_URL=$(git remote get-url origin 2>/dev/null || echo "")
|
||||
fi
|
||||
|
||||
if [[ -z "$TASK_PREFIX" ]]; then
|
||||
# Generate prefix from project name initials
|
||||
TASK_PREFIX=$(echo "$PROJECT_NAME" | sed 's/[^A-Za-z ]//g' | awk '{for(i=1;i<=NF;i++) printf toupper(substr($i,1,1))}')
|
||||
if [[ -z "$TASK_PREFIX" ]]; then
|
||||
TASK_PREFIX="PRJ"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ -z "$PROJECT_DESCRIPTION" ]]; then
|
||||
PROJECT_DESCRIPTION="$PROJECT_NAME"
|
||||
fi
|
||||
|
||||
PROJECT_DIR=$(basename "$(pwd)")
|
||||
|
||||
# Detect quality gates, source dir, and stack info based on type
|
||||
case "$PROJECT_TYPE" in
|
||||
nestjs-nextjs)
|
||||
export QUALITY_GATES="pnpm typecheck && pnpm lint && pnpm test"
|
||||
export SOURCE_DIR="apps"
|
||||
export BUILD_COMMAND="pnpm build"
|
||||
export TEST_COMMAND="pnpm test"
|
||||
export LINT_COMMAND="pnpm lint"
|
||||
export TYPECHECK_COMMAND="pnpm typecheck"
|
||||
export FRONTEND_STACK="Next.js + React + TailwindCSS + Shadcn/ui"
|
||||
export BACKEND_STACK="NestJS + Prisma ORM"
|
||||
export DATABASE_STACK="PostgreSQL"
|
||||
export TESTING_STACK="Vitest + Playwright"
|
||||
export DEPLOYMENT_STACK="Docker + docker-compose"
|
||||
export CONFIG_FILES="turbo.json, pnpm-workspace.yaml, tsconfig.json"
|
||||
;;
|
||||
django)
|
||||
export QUALITY_GATES="ruff check . && mypy . && pytest tests/"
|
||||
export SOURCE_DIR="src"
|
||||
export BUILD_COMMAND="pip install -e ."
|
||||
export TEST_COMMAND="pytest tests/"
|
||||
export LINT_COMMAND="ruff check ."
|
||||
export TYPECHECK_COMMAND="mypy ."
|
||||
export FRONTEND_STACK="N/A"
|
||||
export BACKEND_STACK="Django / Django REST Framework"
|
||||
export DATABASE_STACK="PostgreSQL"
|
||||
export TESTING_STACK="pytest + pytest-django"
|
||||
export DEPLOYMENT_STACK="Docker + docker-compose"
|
||||
export CONFIG_FILES="pyproject.toml"
|
||||
export PROJECT_SLUG=$(echo "$PROJECT_NAME" | tr '[:upper:]' '[:lower:]' | tr ' ' '_' | sed 's/[^a-z0-9_]//g')
|
||||
;;
|
||||
typescript)
|
||||
PKG_MGR="npm"
|
||||
[[ -f "pnpm-lock.yaml" ]] && PKG_MGR="pnpm"
|
||||
[[ -f "yarn.lock" ]] && PKG_MGR="yarn"
|
||||
export QUALITY_GATES="$PKG_MGR run lint && $PKG_MGR run typecheck && $PKG_MGR test"
|
||||
export SOURCE_DIR="src"
|
||||
export BUILD_COMMAND="$PKG_MGR run build"
|
||||
export TEST_COMMAND="$PKG_MGR test"
|
||||
export LINT_COMMAND="$PKG_MGR run lint"
|
||||
export TYPECHECK_COMMAND="npx tsc --noEmit"
|
||||
export FRAMEWORK="TypeScript"
|
||||
export PACKAGE_MANAGER="$PKG_MGR"
|
||||
export FRONTEND_STACK="N/A"
|
||||
export BACKEND_STACK="N/A"
|
||||
export DATABASE_STACK="N/A"
|
||||
export TESTING_STACK="Vitest or Jest"
|
||||
export DEPLOYMENT_STACK="TBD"
|
||||
export CONFIG_FILES="tsconfig.json, package.json"
|
||||
# Detect Next.js
|
||||
if grep -q '"next"' package.json 2>/dev/null; then
|
||||
export FRAMEWORK="Next.js"
|
||||
export FRONTEND_STACK="Next.js + React"
|
||||
fi
|
||||
;;
|
||||
python-fastapi)
|
||||
export PROJECT_SLUG=$(echo "$PROJECT_NAME" | tr '[:upper:]' '[:lower:]' | tr ' ' '_' | sed 's/[^a-z0-9_]//g')
|
||||
export QUALITY_GATES="uv run ruff check src/ tests/ && uv run ruff format --check src/ && uv run mypy src/ && uv run pytest --cov"
|
||||
export SOURCE_DIR="src"
|
||||
export BUILD_COMMAND="uv sync --all-extras"
|
||||
export TEST_COMMAND="uv run pytest --cov"
|
||||
export LINT_COMMAND="uv run ruff check src/ tests/"
|
||||
export TYPECHECK_COMMAND="uv run mypy src/"
|
||||
export FRONTEND_STACK="N/A"
|
||||
export BACKEND_STACK="FastAPI"
|
||||
export DATABASE_STACK="TBD"
|
||||
export TESTING_STACK="pytest + httpx"
|
||||
export DEPLOYMENT_STACK="Docker"
|
||||
export CONFIG_FILES="pyproject.toml"
|
||||
;;
|
||||
python-library)
|
||||
export PROJECT_SLUG=$(echo "$PROJECT_NAME" | tr '[:upper:]' '[:lower:]' | tr ' ' '_' | sed 's/[^a-z0-9_]//g')
|
||||
export QUALITY_GATES="uv run ruff check src/ tests/ && uv run ruff format --check src/ && uv run mypy src/ && uv run pytest --cov"
|
||||
export SOURCE_DIR="src"
|
||||
export BUILD_COMMAND="uv sync --all-extras"
|
||||
export TEST_COMMAND="uv run pytest --cov"
|
||||
export LINT_COMMAND="uv run ruff check src/ tests/"
|
||||
export TYPECHECK_COMMAND="uv run mypy src/"
|
||||
export BUILD_SYSTEM="hatchling"
|
||||
export FRONTEND_STACK="N/A"
|
||||
export BACKEND_STACK="N/A"
|
||||
export DATABASE_STACK="N/A"
|
||||
export TESTING_STACK="pytest"
|
||||
export DEPLOYMENT_STACK="PyPI / Gitea Packages"
|
||||
export CONFIG_FILES="pyproject.toml"
|
||||
;;
|
||||
*)
|
||||
export QUALITY_GATES="echo 'No quality gates configured — update CLAUDE.md'"
|
||||
export SOURCE_DIR="src"
|
||||
export BUILD_COMMAND="echo 'No build command configured'"
|
||||
export TEST_COMMAND="echo 'No test command configured'"
|
||||
export LINT_COMMAND="echo 'No lint command configured'"
|
||||
export TYPECHECK_COMMAND="echo 'No typecheck command configured'"
|
||||
export FRONTEND_STACK="TBD"
|
||||
export BACKEND_STACK="TBD"
|
||||
export DATABASE_STACK="TBD"
|
||||
export TESTING_STACK="TBD"
|
||||
export DEPLOYMENT_STACK="TBD"
|
||||
export CONFIG_FILES="TBD"
|
||||
;;
|
||||
esac
|
||||
|
||||
# Export common variables
|
||||
export PROJECT_NAME
|
||||
export PROJECT_DESCRIPTION
|
||||
export PROJECT_DIR
|
||||
export REPO_URL
|
||||
export TASK_PREFIX
|
||||
|
||||
echo "=== Project Bootstrap ==="
|
||||
echo " Name: $PROJECT_NAME"
|
||||
echo " Type: $PROJECT_TYPE"
|
||||
echo " Prefix: $TASK_PREFIX"
|
||||
echo " Description: $PROJECT_DESCRIPTION"
|
||||
echo " Repo: ${REPO_URL:-'(not set)'}"
|
||||
echo " Directory: $(pwd)"
|
||||
echo ""
|
||||
|
||||
# Select template directory
|
||||
STACK_TEMPLATE_DIR="$TEMPLATE_DIR/projects/$PROJECT_TYPE"
|
||||
if [[ ! -d "$STACK_TEMPLATE_DIR" ]]; then
|
||||
STACK_TEMPLATE_DIR="$TEMPLATE_DIR"
|
||||
echo "No stack-specific templates found for '$PROJECT_TYPE', using generic templates."
|
||||
fi
|
||||
|
||||
if [[ "$DRY_RUN" == true ]]; then
|
||||
echo "[DRY RUN] Would create:"
|
||||
echo " - Validate sequential-thinking MCP hard requirement"
|
||||
echo " - CLAUDE.md (from $STACK_TEMPLATE_DIR/CLAUDE.md.template)"
|
||||
echo " - AGENTS.md (from $STACK_TEMPLATE_DIR/AGENTS.md.template)"
|
||||
echo " - docs/scratchpads/"
|
||||
echo " - docs/reports/qa-automation/{pending,in-progress,done,escalated}"
|
||||
echo " - docs/reports/deferred/"
|
||||
echo " - docs/tasks/"
|
||||
echo " - docs/releases/"
|
||||
echo " - docs/templates/"
|
||||
if [[ "$SKIP_CI" != true ]]; then
|
||||
echo " - .woodpecker/codex-review.yml"
|
||||
echo " - .woodpecker/schemas/*.json"
|
||||
fi
|
||||
if [[ "$SKIP_LABELS" != true ]]; then
|
||||
echo " - Standard git labels (epic, feature, bug, task, documentation, security, breaking)"
|
||||
echo " - Milestone: 0.0.1 - Pre-MVP Foundation"
|
||||
echo " - Milestone policy: 0.0.x pre-MVP, 0.1.0 for MVP release"
|
||||
fi
|
||||
if [[ "$CICD_DOCKER" == true ]]; then
|
||||
echo " - Docker build/push/link steps appended to .woodpecker.yml"
|
||||
for svc in "${CICD_SERVICES[@]}"; do
|
||||
echo " - docker-build-${svc%%:*}"
|
||||
done
|
||||
echo " - link-packages"
|
||||
fi
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Enforce sequential-thinking MCP hard requirement.
|
||||
if [[ ! -x "$SEQUENTIAL_MCP_SCRIPT" ]]; then
|
||||
echo "Error: Missing sequential-thinking setup helper: $SEQUENTIAL_MCP_SCRIPT" >&2
|
||||
echo "Install/repair Mosaic at ~/.config/mosaic before bootstrapping projects." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if "$SEQUENTIAL_MCP_SCRIPT" >/dev/null 2>&1; then
|
||||
echo "Verified sequential-thinking MCP configuration"
|
||||
else
|
||||
echo "Error: sequential-thinking MCP setup failed (hard requirement)." >&2
|
||||
echo "Run: $SEQUENTIAL_MCP_SCRIPT" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Create CLAUDE.md
|
||||
if [[ -f "CLAUDE.md" ]]; then
|
||||
echo "CLAUDE.md already exists — skipping (rename or delete to recreate)"
|
||||
else
|
||||
if [[ -f "$STACK_TEMPLATE_DIR/CLAUDE.md.template" ]]; then
|
||||
envsubst < "$STACK_TEMPLATE_DIR/CLAUDE.md.template" > CLAUDE.md
|
||||
echo "Created CLAUDE.md"
|
||||
else
|
||||
echo "Warning: No CLAUDE.md template found at $STACK_TEMPLATE_DIR" >&2
|
||||
fi
|
||||
fi
|
||||
|
||||
# Create AGENTS.md
|
||||
if [[ -f "AGENTS.md" ]]; then
|
||||
echo "AGENTS.md already exists — skipping (rename or delete to recreate)"
|
||||
else
|
||||
if [[ -f "$STACK_TEMPLATE_DIR/AGENTS.md.template" ]]; then
|
||||
envsubst < "$STACK_TEMPLATE_DIR/AGENTS.md.template" > AGENTS.md
|
||||
echo "Created AGENTS.md"
|
||||
else
|
||||
echo "Warning: No AGENTS.md template found at $STACK_TEMPLATE_DIR" >&2
|
||||
fi
|
||||
fi
|
||||
|
||||
# Create directories
|
||||
mkdir -p \
|
||||
docs/scratchpads \
|
||||
docs/reports/qa-automation/pending \
|
||||
docs/reports/qa-automation/in-progress \
|
||||
docs/reports/qa-automation/done \
|
||||
docs/reports/qa-automation/escalated \
|
||||
docs/reports/deferred \
|
||||
docs/tasks \
|
||||
docs/releases \
|
||||
docs/templates
|
||||
echo "Created docs/scratchpads/, docs/reports/*, docs/tasks/, docs/releases/, docs/templates/"
|
||||
|
||||
# Set up CI/CD pipeline
|
||||
if [[ "$SKIP_CI" != true ]]; then
|
||||
CODEX_DIR="$HOME/.config/mosaic/tools/codex"
|
||||
if [[ -d "$CODEX_DIR/woodpecker" ]]; then
|
||||
mkdir -p .woodpecker/schemas
|
||||
cp "$CODEX_DIR/woodpecker/codex-review.yml" .woodpecker/
|
||||
cp "$CODEX_DIR/schemas/"*.json .woodpecker/schemas/
|
||||
echo "Created .woodpecker/ with Codex review pipeline"
|
||||
else
|
||||
echo "Codex pipeline templates not found — skipping CI setup"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Generate Docker build/push/link pipeline steps
|
||||
if [[ "$CICD_DOCKER" == true ]]; then
|
||||
CICD_SCRIPT="$HOME/.config/mosaic/tools/cicd/generate-docker-steps.sh"
|
||||
if [[ -x "$CICD_SCRIPT" ]]; then
|
||||
# Parse org and repo from git remote
|
||||
CICD_REGISTRY=""
|
||||
CICD_ORG=""
|
||||
CICD_REPO_NAME=""
|
||||
if [[ -n "$REPO_URL" ]]; then
|
||||
# Extract host from https://host/org/repo.git or git@host:org/repo.git
|
||||
CICD_REGISTRY=$(echo "$REPO_URL" | sed -E 's|https?://([^/]+)/.*|\1|; s|git@([^:]+):.*|\1|')
|
||||
CICD_ORG=$(echo "$REPO_URL" | sed -E 's|https?://[^/]+/([^/]+)/.*|\1|; s|git@[^:]+:([^/]+)/.*|\1|')
|
||||
CICD_REPO_NAME=$(echo "$REPO_URL" | sed -E 's|\.git$||' | sed -E 's|.*/([^/]+)$|\1|')
|
||||
fi
|
||||
|
||||
if [[ -n "$CICD_REGISTRY" && -n "$CICD_ORG" && -n "$CICD_REPO_NAME" && ${#CICD_SERVICES[@]} -gt 0 ]]; then
|
||||
# Build service args
|
||||
SVC_ARGS=""
|
||||
for svc in "${CICD_SERVICES[@]}"; do
|
||||
SVC_ARGS="$SVC_ARGS --service $svc"
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo "Generating Docker CI/CD pipeline steps..."
|
||||
|
||||
# Add kaniko_setup anchor to variables section if .woodpecker.yml exists
|
||||
if [[ -f ".woodpecker.yml" ]]; then
|
||||
# Append Docker steps to existing pipeline
|
||||
"$CICD_SCRIPT" \
|
||||
--registry "$CICD_REGISTRY" \
|
||||
--org "$CICD_ORG" \
|
||||
--repo "$CICD_REPO_NAME" \
|
||||
$SVC_ARGS \
|
||||
--branches "$CICD_BRANCHES" >> .woodpecker.yml
|
||||
echo "Appended Docker build/push/link steps to .woodpecker.yml"
|
||||
else
|
||||
echo "Warning: No .woodpecker.yml found — generate quality gates first, then re-run with --cicd-docker" >&2
|
||||
fi
|
||||
else
|
||||
if [[ ${#CICD_SERVICES[@]} -eq 0 ]]; then
|
||||
echo "Warning: --cicd-docker requires at least one --cicd-service" >&2
|
||||
else
|
||||
echo "Warning: Could not parse registry/org/repo from git remote — specify --repo" >&2
|
||||
fi
|
||||
fi
|
||||
else
|
||||
echo "Docker CI/CD generator not found at $CICD_SCRIPT — skipping" >&2
|
||||
fi
|
||||
fi
|
||||
|
||||
# Initialize labels and milestones
|
||||
if [[ "$SKIP_LABELS" != true ]]; then
|
||||
LABEL_SCRIPT="$SCRIPT_DIR/init-repo-labels.sh"
|
||||
if [[ -x "$LABEL_SCRIPT" ]]; then
|
||||
echo ""
|
||||
echo "Initializing git labels and milestones..."
|
||||
"$LABEL_SCRIPT"
|
||||
else
|
||||
echo "Label init script not found — skipping label setup"
|
||||
fi
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "=== Bootstrap Complete ==="
|
||||
echo ""
|
||||
echo "Next steps:"
|
||||
echo " 1. Review and customize CLAUDE.md"
|
||||
echo " 2. Review and customize AGENTS.md"
|
||||
echo " 3. Update quality gate commands if needed"
|
||||
echo " 4. Commit: git add CLAUDE.md AGENTS.md docs/ .woodpecker/ && git commit -m 'feat: Bootstrap project for AI development'"
|
||||
if [[ "$SKIP_CI" != true ]]; then
|
||||
echo " 5. Add 'codex_api_key' secret to Woodpecker CI"
|
||||
fi
|
||||
if [[ "$CICD_DOCKER" == true ]]; then
|
||||
echo " 6. Add 'gitea_username' and 'gitea_token' secrets to Woodpecker CI"
|
||||
echo " (token needs package:write scope)"
|
||||
fi
|
||||
123
packages/mosaic/framework/tools/bootstrap/init-repo-labels.sh
Executable file
123
packages/mosaic/framework/tools/bootstrap/init-repo-labels.sh
Executable file
@@ -0,0 +1,123 @@
|
||||
#!/bin/bash
|
||||
# init-repo-labels.sh - Create standard labels and initial milestone for a repository
|
||||
# Usage: init-repo-labels.sh [--skip-milestone]
|
||||
#
|
||||
# Works with both Gitea (tea) and GitHub (gh).
|
||||
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
GIT_SCRIPT_DIR="$HOME/.config/mosaic/tools/git"
|
||||
source "$GIT_SCRIPT_DIR/detect-platform.sh"
|
||||
|
||||
SKIP_MILESTONE=false
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case $1 in
|
||||
--skip-milestone)
|
||||
SKIP_MILESTONE=true
|
||||
shift
|
||||
;;
|
||||
-h|--help)
|
||||
echo "Usage: $(basename "$0") [--skip-milestone]"
|
||||
echo ""
|
||||
echo "Create standard labels and initial milestone for the current repository."
|
||||
echo ""
|
||||
echo "Options:"
|
||||
echo " --skip-milestone Skip creating the 0.0.1 pre-MVP milestone"
|
||||
echo " -h, --help Show this help"
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
echo "Unknown option: $1" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
PLATFORM=$(detect_platform)
|
||||
OWNER=$(get_repo_owner)
|
||||
REPO=$(get_repo_name)
|
||||
|
||||
echo "Platform: $PLATFORM"
|
||||
echo "Repository: $OWNER/$REPO"
|
||||
echo ""
|
||||
|
||||
# Standard labels with colors
|
||||
# Format: "name|color|description"
|
||||
LABELS=(
|
||||
"epic|3E4B9E|Large feature spanning multiple issues"
|
||||
"feature|0E8A16|New functionality"
|
||||
"bug|D73A4A|Defect fix"
|
||||
"task|0075CA|General work item"
|
||||
"documentation|0075CA|Documentation updates"
|
||||
"security|B60205|Security-related"
|
||||
"breaking|D93F0B|Breaking change"
|
||||
)
|
||||
|
||||
create_label_github() {
|
||||
local name="$1" color="$2" description="$3"
|
||||
|
||||
# Check if label already exists
|
||||
if gh label list --repo "$OWNER/$REPO" --json name -q ".[].name" 2>/dev/null | grep -qx "$name"; then
|
||||
echo " [skip] '$name' already exists"
|
||||
return 0
|
||||
fi
|
||||
|
||||
gh label create "$name" \
|
||||
--repo "$OWNER/$REPO" \
|
||||
--color "$color" \
|
||||
--description "$description" 2>/dev/null && \
|
||||
echo " [created] '$name'" || \
|
||||
echo " [error] Failed to create '$name'"
|
||||
}
|
||||
|
||||
create_label_gitea() {
|
||||
local name="$1" color="$2" description="$3"
|
||||
|
||||
# Check if label already exists
|
||||
if tea labels list 2>/dev/null | grep -q "$name"; then
|
||||
echo " [skip] '$name' already exists"
|
||||
return 0
|
||||
fi
|
||||
|
||||
tea labels create --name "$name" --color "#$color" --description "$description" 2>/dev/null && \
|
||||
echo " [created] '$name'" || \
|
||||
echo " [error] Failed to create '$name'"
|
||||
}
|
||||
|
||||
echo "Creating labels..."
|
||||
|
||||
for label_def in "${LABELS[@]}"; do
|
||||
IFS='|' read -r name color description <<< "$label_def"
|
||||
|
||||
case "$PLATFORM" in
|
||||
github)
|
||||
create_label_github "$name" "$color" "$description"
|
||||
;;
|
||||
gitea)
|
||||
create_label_gitea "$name" "$color" "$description"
|
||||
;;
|
||||
*)
|
||||
echo "Error: Unsupported platform '$PLATFORM'" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
echo ""
|
||||
|
||||
# Create initial pre-MVP milestone
|
||||
if [[ "$SKIP_MILESTONE" != true ]]; then
|
||||
echo "Creating initial pre-MVP milestone..."
|
||||
|
||||
"$GIT_SCRIPT_DIR/milestone-create.sh" -t "0.0.1" -d "Pre-MVP - Foundation Sprint" 2>/dev/null && \
|
||||
echo " [created] Milestone '0.0.1 - Pre-MVP'" || \
|
||||
echo " [skip] Milestone may already exist or creation failed"
|
||||
|
||||
echo " [note] Reserve 0.1.0 for MVP release milestone"
|
||||
|
||||
echo ""
|
||||
fi
|
||||
|
||||
echo "Label initialization complete."
|
||||
Reference in New Issue
Block a user