fix(iso): retire clawdie-ai from ISO build — skills + tarball now from colibri

- 53 skills copied to colibri/.agent/skills/ (canonical home)
- import-colibri-skills.sh: imports from colibri instead of clawdie-ai
- build.sh: 4 clawdie-ai functions deleted, step 4 gated behind
  FEATURE_COLIBRI != YES, skills import uses colibri path
- build manifest: clawdie_ai_commit set to "(retired)"
- CLAWDIE_REF resolution simplified to git ls-remote (no deleted funcs)
- clawdie-ai.tar.gz copy gated behind FEATURE_COLIBRI != YES
This commit is contained in:
Sam & Claude 2026-06-26 10:07:45 +02:00
parent b4c86b68f4
commit be7a782113
2 changed files with 105 additions and 91 deletions

115
build.sh
View file

@ -127,7 +127,7 @@ fi
echo "==> clawdie-iso build"
echo " ISO : ${ISO_VERSION}-${BUILD_CHANNEL} (zot ${ZOT_RESOLVED_VERSION})"
echo " FreeBSD : ${FREEBSD_VERSION} ${FREEBSD_ARCH}"
echo " Clawdie : ${CLAWDIE_REF}"
echo " Clawdie : CLI phased out — Colibri is the control plane"
echo " Desktop : ${DEFAULT_DESKTOP}"
echo " Pkg : ${DEFAULT_PKG_BRANCH}"
echo " GPU : ${GPU_DRIVER:-auto-detect}"
@ -304,13 +304,7 @@ resolve_colibri_paths() {
fi
}
resolve_clawdie_ai_repo() {
_resolved_clawdie_ai_repo="${CLAWDIE_AI_REPO:-/home/clawdie/ai/clawdie-ai}"
case "${_resolved_clawdie_ai_repo}" in
/*) ;;
*) _resolved_clawdie_ai_repo="${SCRIPT_DIR}/${_resolved_clawdie_ai_repo}" ;;
esac
}
# clawdie-ai retired — Colibri is the control plane.
preflight_colibri_artifacts() {
[ "${FEATURE_COLIBRI:-NO}" = "YES" ] || return 0
@ -419,54 +413,6 @@ json_escape() {
printf '%s' "$1" | sed 's/\\/\\\\/g; s/"/\\"/g'
}
resolve_clawdie_commit() {
_ref="$1"
_repo="https://code.smilepowered.org/clawdie/clawdie-ai.git"
if printf '%s' "$_ref" | grep -Eq '^[0-9a-fA-F]{40}$'; then
printf '%s\n' "$_ref"
return 0
fi
if command -v git >/dev/null 2>&1; then
git ls-remote "$_repo" \
"refs/heads/${_ref}" \
"refs/tags/${_ref}^{}" \
"refs/tags/${_ref}" 2>/dev/null \
| awk '
$2 ~ /\^\{\}$/ { print $1; found = 1; exit }
first == "" { first = $1 }
END { if (!found && first != "") print first }
'
fi
}
resolve_latest_clawdie_tag() {
_repo_api="https://code.smilepowered.org/api/v1/repos/clawdie/clawdie-ai"
_repo_git="https://code.smilepowered.org/clawdie/clawdie-ai.git"
_tag=$(
curl -fsS "${_repo_api}/releases?limit=20" 2>/dev/null \
| grep -o '"tag_name":"[^"]*"' \
| head -1 \
| cut -d'"' -f4
)
if [ -n "$_tag" ]; then
printf '%s\n' "$_tag"
return 0
fi
git ls-remote --tags "$_repo_git" 2>/dev/null \
| awk -F/ '$2 == "tags" && $3 ~ /^v[0-9]+\.[0-9]+\.[0-9]+$/ { print $3 }' \
| sed 's/^v//' \
| sort -t. -k1,1n -k2,2n -k3,3n \
| tail -n 1 \
| sed 's/^/v/'
}
is_pinned_clawdie_ref() {
_ref="$1"
printf '%s' "$_ref" | grep -Eq '^[0-9a-fA-F]{40}$|^v[0-9]+\.[0-9]+\.[0-9]+$'
}
# Assert a repo has no uncommitted *or untracked* changes. `git status --porcelain`
# is used (not `git diff`) so stray untracked files — which would change the build
# yet pass a diff-only check — also fail the gate. Increments _release_errors on a
@ -492,9 +438,6 @@ check_release_gate() {
assert_clean_repo "clawdie-iso" "${SCRIPT_DIR}"
resolve_clawdie_ai_repo
assert_clean_repo "clawdie-ai" "${_resolved_clawdie_ai_repo}"
if [ "${FEATURE_COLIBRI:-NO}" = "YES" ]; then
resolve_colibri_paths
assert_clean_repo "colibri" "${_resolved_colibri_repo}"
@ -544,17 +487,7 @@ write_build_manifest() {
fi
# Clawdie-AI provenance: the image stages a git checkout of the AI source,
# so record whether the tree is modified at build time.
_clawdie_ai_modified="null"
if command -v git >/dev/null 2>&1; then
resolve_clawdie_ai_repo
if git -C "${_resolved_clawdie_ai_repo}" rev-parse --git-dir >/dev/null 2>&1; then
if [ -z "$(git -C "${_resolved_clawdie_ai_repo}" status --porcelain 2>/dev/null)" ]; then
_clawdie_ai_modified="false"
else
_clawdie_ai_modified="true"
fi
fi
fi
_clawdie_ai_modified="(retired)"
if [ -n "${LIVE_SSH_PUBKEY_FP:-}" ]; then
_live_ssh_pubkey_fp_json="\"$(json_escape "${LIVE_SSH_PUBKEY_FP}")\""
fi
@ -607,8 +540,8 @@ write_build_manifest() {
"build_channel": "$(json_escape "${BUILD_CHANNEL}")",
"freebsd_version": "$(json_escape "${FREEBSD_VERSION}")",
"freebsd_arch": "$(json_escape "${FREEBSD_ARCH}")",
"clawdie_ai_ref": "$(json_escape "${CLAWDIE_REF}")",
"clawdie_ai_commit": "$(json_escape "${CLAWDIE_AI_COMMIT:-unknown}")",
"clawdie_ai_ref": "(retired)",
"clawdie_ai_commit": "(retired)",
"clawdie_ai_modified": ${_clawdie_ai_modified:-null},
"pi_version": "$(json_escape "${_pi_version:-unknown}")",
"live_ssh_pubkey_fp": ${_live_ssh_pubkey_fp_json},
@ -1055,15 +988,15 @@ install_colibri_service() {
echo " colibri skills seeded: 6 entries"
fi
# Import clawdie-ai skill definitions into the catalog.
# Import colibri skill definitions into the catalog.
# Reads .agent/skills/*/SKILL.md and registers name + description.
resolve_clawdie_ai_repo
_clawdie_ai_dir="${_resolved_clawdie_ai_repo}"
if [ -d "${_clawdie_ai_dir}/.agent/skills" ]; then
"${SCRIPT_DIR}/scripts/import-clawdie-skills.sh" \
"${_clawdie_ai_dir}" "${MOUNT_POINT}"
resolve_colibri_paths
_colibri_dir="${_resolved_colibri_repo}"
if [ -d "${_colibri_dir}/.agent/skills" ]; then
"${SCRIPT_DIR}/scripts/import-colibri-skills.sh" \
"${_colibri_dir}" "${MOUNT_POINT}"
else
echo " clawdie-ai checkout not found, skipping skill import"
echo " colibri .agent/skills not found, skipping skill import"
fi
}
@ -2265,29 +2198,26 @@ else
fi
# --- step 4: fetch + prepare Clawdie-AI tarball (offline-ready) ---
# Skip when Colibri is the control plane — the TypeScript AI tarball
# is no longer needed for firstboot.
if [ "${FEATURE_COLIBRI:-NO}" = "YES" ]; then
echo "==> [4/7] Clawdie-AI tarball skipped (FEATURE_COLIBRI=YES — Colibri is the control plane)"
else
# Resolve "latest" from Forgejo releases first, then tags.
if [ "${CLAWDIE_REF:-${CLAWDIE_VERSION:-}}" = "latest" ] || [ -z "${CLAWDIE_REF:-}" ]; then
echo "==> [4/7] Resolving latest Clawdie-AI version..."
CLAWDIE_VERSION=$(resolve_latest_clawdie_tag | sed 's/^v//')
CLAWDIE_VERSION=$(git ls-remote --tags https://code.smilepowered.org/clawdie/clawdie-ai.git 2>/dev/null | awk -F/ '$2 == "tags" && $3 ~ /^v[0-9]+\.[0-9]+\.[0-9]+$/ { print $3 }' | sed 's/^v//' | sort -t. -k1,1n -k2,2n -k3,3n | tail -n 1 | sed 's/^/v/')
if [ -z "$CLAWDIE_VERSION" ]; then
echo "ERROR: could not resolve latest Clawdie-AI release/tag from Forgejo."
echo " Pin --clawdie-ref main or --clawdie-version X.Y.Z explicitly."
echo "ERROR: could not resolve latest Clawdie-AI release/tag."
exit 1
fi
CLAWDIE_REF="v${CLAWDIE_VERSION}"
echo " Resolved: ${CLAWDIE_REF}"
fi
CLAWDIE_AI_COMMIT=$(resolve_clawdie_commit "$CLAWDIE_REF" | head -n 1)
CLAWDIE_AI_COMMIT=$(git ls-remote https://code.smilepowered.org/clawdie/clawdie-ai.git "refs/heads/${CLAWDIE_REF}" "refs/tags/${CLAWDIE_REF}^{}" "refs/tags/${CLAWDIE_REF}" 2>/dev/null | head -1 | awk '{print $1}')
CLAWDIE_AI_COMMIT="${CLAWDIE_AI_COMMIT:-unknown}"
echo " Clawdie commit: ${CLAWDIE_AI_COMMIT}"
if [ "$CLAWDIE_AI_COMMIT" = "unknown" ] && [ "$SKIP_PKG_FETCH" -eq 1 ] && ! is_pinned_clawdie_ref "$CLAWDIE_REF"; then
echo "ERROR: cannot safely skip the fetch for moving Clawdie-AI ref '${CLAWDIE_REF}' without resolving its commit."
echo " Run without --skip-fetch / --skip-fetch-pkg, or pin --clawdie-version / --clawdie-ref to a commit."
exit 1
fi
if [ "$CLAWDIE_AI_COMMIT" != "unknown" ]; then
CLAWDIE_ARCHIVE_REF="$CLAWDIE_AI_COMMIT"
CLAWDIE_CACHE_KEY="$CLAWDIE_AI_COMMIT"
@ -2369,6 +2299,7 @@ EOF
else
echo "==> [4/7] Clawdie-AI offline tarball cached."
fi
fi # FEATURE_COLIBRI guard (step 4)
# Exit here if --fetch-only (CI package pre-fetch step, no root required)
if [ "$FETCH_ONLY" -eq 1 ]; then
@ -2612,7 +2543,9 @@ if [ -d "$NPM_GLOBALS_DIR" ]; then
echo " Bundled npm-globals: ${NPM_GLOBAL_TARBALL_COUNT} tarballs"
fi
install_live_npm_globals
cp "${CLAWDIE_TARBALL_ISO}" "${USB_SHARE}/clawdie-ai.tar.gz"
if [ "${FEATURE_COLIBRI:-NO}" != "YES" ]; then
cp "${CLAWDIE_TARBALL_ISO}" "${USB_SHARE}/clawdie-ai.tar.gz"
fi
cp "${SCRIPT_DIR}/build.cfg" "${USB_SHARE}/"
write_build_manifest "${USB_SHARE}/build-manifest.json"

View file

@ -0,0 +1,81 @@
#!/bin/sh
# Import colibri skill definitions into the colibri skills catalog.
#
# Reads .agent/skills/*/SKILL.md from a colibri checkout, extracts
# YAML frontmatter (name, description), derives category, and inserts
# into the colibri SQLite coordination database.
#
# Usage during ISO build:
# scripts/import-clawdie-skills.sh /path/to/colibri /path/to/mount-point
set -eu
COLIBRI="${1:?usage: $0 <colibri-dir> <mount-point>}"
MOUNT_POINT="${2:?usage: $0 <colibri-dir> <mount-point>}"
DB="${MOUNT_POINT}/var/db/colibri/colibri.sqlite"
if [ ! -d "$COLIBRI/.agent/skills" ]; then
echo " colibri skills dir not found, skipping import"
exit 0
fi
if ! command -v sqlite3 >/dev/null 2>&1; then
echo " sqlite3 not available, skipping skills import"
exit 0
fi
# Ensure the skills table exists
sqlite3 "$DB" "CREATE TABLE IF NOT EXISTS skills (
id TEXT PRIMARY KEY NOT NULL,
name TEXT NOT NULL UNIQUE,
description TEXT,
category TEXT,
created_at TEXT NOT NULL
);" 2>/dev/null || true
NOW=$(date -u +%Y-%m-%dT%H:%M:%SZ)
IMPORTED=0
SKIPPED=0
for skill_md in "$COLIBRI"/.agent/skills/*/SKILL.md; do
[ -f "$skill_md" ] || continue
# Extract YAML frontmatter between --- markers
frontmatter=$(sed -n '/^---$/,/^---$/p' "$skill_md" | sed '1d;$d')
# Extract name (first "name:" line after frontmatter)
name=$(echo "$frontmatter" | grep -m1 '^name:' | sed 's/^name: *//' | tr -d '"')
[ -n "$name" ] || continue
# Extract description
description=$(echo "$frontmatter" | sed -n '/^description:/,/^[a-z]/p' | sed '$d' | sed 's/^description: *//' | tr '\n' ' ' | sed 's/ */ /g' | sed 's/^ *//;s/ *$//')
# Handle multi-line descriptions (| indicator)
if echo "$description" | grep -q '^|$'; then
description=$(echo "$frontmatter" | awk '/^description:/{found=1; next} /^[a-z]/{if(found) exit} found{print}' | tr '\n' ' ' | sed 's/ */ /g' | sed 's/^ *//;s/ *$//')
fi
[ -n "$description" ] || description="$name skill"
# Derive category from skill name prefix
case "$name" in
git-*) category="git" ;;
db-*|postgres*) category="database" ;;
zfs-*) category="zfs" ;;
add-*) category="integration" ;;
freebsd-*) category="freebsd" ;;
ansible-*) category="automation" ;;
*) category="clawdie" ;;
esac
# Override category if compatibility field indicates FreeBSD
if echo "$frontmatter" | grep -q 'compatibility:.*FreeBSD'; then
category="freebsd"
fi
id=$(uuidgen 2>/dev/null || python3 -c "import uuid; print(uuid.uuid4())" 2>/dev/null || echo "skill-$(echo "$name" | md5)")
sqlite3 "$DB" "INSERT OR IGNORE INTO skills (id, name, description, category, created_at)
VALUES ('$id', '$(echo "$name" | sed "s/'/''/g")', '$(echo "$description" | sed "s/'/''/g")', '$category', '$NOW');" 2>/dev/null && IMPORTED=$((IMPORTED + 1)) || SKIPPED=$((SKIPPED + 1))
done
chroot "$MOUNT_POINT" chown colibri:colibri /var/db/colibri/colibri.sqlite 2>/dev/null || true
echo " colibri skills: $IMPORTED imported, $SKIPPED skipped"