Files
claude-adam/install.sh
T
lukaszraczylo 440fb52eb1 feat: apply MOSS-grounded self-evolution improvements to ADAM
Implements 7 improvements grounded in MOSS paper (arXiv 2605.22794):

1. Transcript capture (§3.4): context_ring buffer in adam-observe.mjs
   captures last 8 events around struggle signals as context_window.

2. Evidence batching (§3.1): new adam-batch.mjs pre-clusters windowed
   journal entries into coherent failure batches by (signal_type, cluster_key).

3. Multi-stage analysis (§3.3): SKILL.md dispatches adam agent in two
   stages (diagnose+plan → implement) with inter-stage validation gate.

4. Pre-apply verification (§3.4): 4-check deterministic gate before
   auto-apply (source entries exist, diagnosis grounded, type-evidence
   match, no conflicting recent proposals).

5. Auto-rollback (§3.5): new adam-rollback.mjs reverts regressed proposals
   detected by A/B measurement, creates regression nudges.

6. Harness self-modification (§1 Table 1): new harness_edit proposal type
   targeting adam's own scripts with stricter gates (confidence≥5, never
   auto-apply, test-suite-gated).

7. Keypoint matrix evaluation (§4.2): 5 capability dimensions
   (tool_selection, scope_discipline, error_recovery, first_attempt,
   build_reliability) scored per batch for structured evaluation.

Test suite: 94 → 114 tests (20 new), all passing.
2026-05-24 11:15:32 +01:00

237 lines
9.4 KiB
Bash
Executable File

#!/usr/bin/env bash
# ADAM installer — pure bash + git + curl + jq.
# Idempotent. Safe for upgrades. Supports `curl | bash` via auto-clone.
#
# Usage:
# ./install.sh # local install from cwd
# curl -fsSL <raw>/install.sh | bash
# VERSION=v0.3.0 ./install.sh # pin a tag
# ./install.sh --yes # skip settings.json prompt
# ./install.sh --dry-run # show actions, write nothing
set -euo pipefail
REPO_GIT="https://github.com/lukaszraczylo/claude-adam.git"
DEST="${HOME}/.claude"
ASSUME_YES=0
DRY_RUN=0
VERSION="${VERSION:-${BRANCH:-}}" # env var pin; empty = latest tag
log() { printf ' %s\n' "$*"; }
warn() { printf ' ! %s\n' "$*" >&2; }
die() { printf ' ! %s\n' "$*" >&2; exit 1; }
run() { if [ "$DRY_RUN" = 1 ]; then printf ' [dry-run] %s\n' "$*"; else eval "$@"; fi; }
# --------------------------------------------------------------------- args
for arg in "$@"; do
case "$arg" in
--yes|-y) ASSUME_YES=1 ;;
--dry-run|-n) DRY_RUN=1 ;;
--version=*) VERSION="${arg#--version=}" ;;
--help|-h) sed -n '2,12p' "$0"; exit 0 ;;
*) die "unknown arg: $arg (try --help)" ;;
esac
done
# --------------------------------------------------------------------- prereqs
need() { command -v "$1" >/dev/null 2>&1 || die "missing: $1$2"; }
need git "install: brew install git || apt install git"
need curl "install: brew install curl || apt install curl"
need jq "install: brew install jq || apt install jq"
command -v node >/dev/null 2>&1 || warn "node not found — hooks need node 18+; install: brew install node"
# --------------------------------------------------------------------- locate source
# If invoked via `curl | bash`, $0 is bash and there are no local files.
PIPED=0
SCRIPT_PATH="${BASH_SOURCE[0]:-$0}"
if [ ! -f "$SCRIPT_PATH" ] || [ "$SCRIPT_PATH" = "bash" ] || [ "$SCRIPT_PATH" = "-" ]; then
PIPED=1
elif [ ! -d "$(dirname "$SCRIPT_PATH")/hooks" ]; then
PIPED=1
fi
CLEANUP_TMP=""
cleanup() { [ -n "$CLEANUP_TMP" ] && rm -rf "$CLEANUP_TMP" 2>/dev/null || true; }
trap cleanup EXIT INT TERM
if [ "$PIPED" = 1 ]; then
log "running via curl|bash — cloning repo to tmp"
CLEANUP_TMP="$(mktemp -d -t claude-adam-install.XXXXXX)"
REF="$VERSION"
if [ -z "$REF" ]; then
# latest semver tag from remote (no local clone needed)
REF="$(git ls-remote --tags --refs "$REPO_GIT" \
| awk -F/ '{print $NF}' \
| grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$' \
| sort -V | tail -1)"
[ -z "$REF" ] && REF="main"
fi
log "fetching $REF"
run "git clone --quiet --depth=1 --branch=\"$REF\" \"$REPO_GIT\" \"$CLEANUP_TMP\""
SRC="$CLEANUP_TMP"
else
SRC="$(cd "$(dirname "$SCRIPT_PATH")" && pwd)"
fi
log "ADAM installer"
log " source: $SRC"
log " dest: $DEST"
log " mode: $([ "$DRY_RUN" = 1 ] && echo dry-run || echo apply)$([ "$ASSUME_YES" = 1 ] && echo ' --yes' || true)"
log ""
[ -d "$DEST" ] || die "$DEST does not exist. Install Claude Code first: https://claude.com/claude-code"
# --------------------------------------------------------------------- dirs
DIRS=(
"hooks" "agents" "skills/adam-self-improvement" "commands"
"adam/proposals" "adam/applied" "adam/rejected" "adam/trash"
"adam/journal" "adam/scripts" "adam/tests/fixtures"
)
for d in "${DIRS[@]}"; do run "mkdir -p \"$DEST/$d\""; done
# .gitkeep markers so the layout survives `git init` for users who VCS ~/.claude
for d in adam/proposals adam/applied adam/rejected adam/trash adam/journal; do
[ -e "$DEST/$d/.gitkeep" ] || run ": > \"$DEST/$d/.gitkeep\""
done
# --------------------------------------------------------------------- file copy
# Conservative: if dest exists and differs from src AND user-modified after install,
# write to <file>.adam-new and warn instead of clobbering.
copy_file() {
local src="$1" dst="$2"
[ -f "$src" ] || die "missing source file: $src"
if [ -f "$dst" ] && ! cmp -s "$src" "$dst"; then
if [ -f "$DEST/adam/.install-marker" ] \
&& [ "$(stat -f %m "$dst" 2>/dev/null || stat -c %Y "$dst")" \
-gt "$(stat -f %m "$DEST/adam/.install-marker" 2>/dev/null || stat -c %Y "$DEST/adam/.install-marker")" ]; then
warn "modified locally, NOT overwriting: $dst"
warn " new version written to: $dst.adam-new (review and merge manually)"
run "cp \"$src\" \"$dst.adam-new\""
return
fi
fi
run "cp \"$src\" \"$dst\""
log " copied: ${dst#$HOME/}"
}
# Hooks
copy_file "$SRC/hooks/adam-observe.mjs" "$DEST/hooks/adam-observe.mjs"
copy_file "$SRC/hooks/adam-nudge.mjs" "$DEST/hooks/adam-nudge.mjs"
# Agent / skill / command
copy_file "$SRC/agents/adam.md" "$DEST/agents/adam.md"
copy_file "$SRC/skills/adam-self-improvement/SKILL.md" "$DEST/skills/adam-self-improvement/SKILL.md"
copy_file "$SRC/commands/reflect.md" "$DEST/commands/reflect.md"
# Adam internals
copy_file "$SRC/adam/scripts/adam-archive.mjs" "$DEST/adam/scripts/adam-archive.mjs"
copy_file "$SRC/adam/scripts/adam-upgrade.mjs" "$DEST/adam/scripts/adam-upgrade.mjs"
# v0.3.3 helper scripts — invoked from SKILL.md / hooks / analyst flow
for _adam_script in adam-utils adam-window adam-explain adam-nudge-eligibility adam-cooldown \
adam-score adam-ab-measure adam-apply-reinforcement adam-batch adam-rollback; do
copy_file "$SRC/adam/scripts/${_adam_script}.mjs" \
"$DEST/adam/scripts/${_adam_script}.mjs"
run "chmod +x \"$DEST/adam/scripts/${_adam_script}.mjs\""
done
run "chmod +x \"$DEST/adam/scripts/adam-upgrade.mjs\""
copy_file "$SRC/adam/tests/run-tests.sh" "$DEST/adam/tests/run-tests.sh"
copy_file "$SRC/adam/tests/fixtures/seed-corrections.jsonl" "$DEST/adam/tests/fixtures/seed-corrections.jsonl"
# Preserve user data — never overwrite
[ -f "$DEST/adam/journal.jsonl" ] || run ": > \"$DEST/adam/journal.jsonl\""
[ -f "$DEST/adam/state.json" ] || run "echo '{\"tool_window\":[]}' > \"$DEST/adam/state.json\""
[ -f "$DEST/adam/usage.json" ] || run "echo '{}' > \"$DEST/adam/usage.json\""
# install marker — used by future runs to detect local mtime drift
run "touch \"$DEST/adam/.install-marker\""
# --------------------------------------------------------------------- settings.json
SETTINGS="$DEST/settings.json"
EXAMPLE="$SRC/settings.json.example"
[ -f "$EXAMPLE" ] || die "missing $EXAMPLE"
# Build target settings via jq merge (preserves all user keys/hooks).
TMP_NEW="$(mktemp -t adam-settings.XXXXXX)"
TMP_DIFF="$(mktemp -t adam-settings-diff.XXXXXX)"
cleanup_full() { cleanup; rm -f "$TMP_NEW" "$TMP_DIFF" 2>/dev/null || true; }
trap cleanup_full EXIT INT TERM
if [ -f "$SETTINGS" ]; then
jq --slurpfile add "$EXAMPLE" '
. as $cur
| ($add[0].hooks // {}) as $new
| .hooks = (
($cur.hooks // {}) as $cur_hooks
| reduce ($new | keys[]) as $k ($cur_hooks;
.[$k] = (
((.[$k] // []) + $new[$k])
| unique_by(tojson)
)
)
)
' "$SETTINGS" > "$TMP_NEW"
else
jq 'del(._comment)' "$EXAMPLE" > "$TMP_NEW"
fi
if [ -f "$SETTINGS" ] && cmp -s "$SETTINGS" "$TMP_NEW"; then
log "settings.json already wired — no changes"
else
log ""
log "settings.json changes proposed:"
if [ -f "$SETTINGS" ]; then
diff -u "$SETTINGS" "$TMP_NEW" > "$TMP_DIFF" || true
else
diff -u /dev/null "$TMP_NEW" > "$TMP_DIFF" || true
fi
sed 's/^/ /' "$TMP_DIFF"
log ""
if [ "$ASSUME_YES" = 1 ]; then
REPLY=y
else
printf ' apply settings.json changes? [y/N] '
read -r REPLY </dev/tty || REPLY=n
fi
case "$REPLY" in
y|Y|yes|YES)
[ -f "$SETTINGS" ] && run "cp \"$SETTINGS\" \"$SETTINGS.adam-bak.$(date +%s)\""
run "mv \"$TMP_NEW\" \"$SETTINGS\""
log " settings.json updated (backup at *.adam-bak.<ts> if pre-existing)"
;;
*)
log " skipped — wire entries from $EXAMPLE manually"
;;
esac
fi
# --------------------------------------------------------------------- summary
log ""
log "installed:"
log " hooks/adam-observe.mjs, hooks/adam-nudge.mjs"
log " agents/adam.md"
log " skills/adam-self-improvement/SKILL.md"
log " commands/reflect.md"
log " adam/scripts/adam-archive.mjs"
log " adam/scripts/adam-upgrade.mjs"
log " adam/tests/run-tests.sh"
log ""
log "preserved (if existed):"
log " adam/journal.jsonl, adam/state.json, adam/usage.json"
log ""
log "next:"
log " 1. bash $DEST/adam/tests/run-tests.sh # expect: all passed"
log " 2. start a fresh Claude Code session"
log " 3. run /reflect to invoke the analyst"
log ""
log "ADAM is dormant until you run /reflect."
log "journal: $DEST/adam/journal.jsonl"
log "proposals: $DEST/adam/proposals/"
# --------------------------------------------------------------------- pending merges
# If this upgrade left any `.adam-new` files behind, make the trap unmissable.
PENDING_COUNT=$(find "$DEST" \( -name .git -o -name node_modules -o -path "*/adam/journal" -o -path "*/adam/trash" -o -path "*/adam/proposals" -o -path "*/adam/applied" -o -path "*/adam/rejected" \) -prune -o -type f -name '*.adam-new' -print 2>/dev/null | wc -l | tr -d ' ')
if [ "${PENDING_COUNT:-0}" -gt 0 ]; then
log ""
warn "${PENDING_COUNT} file(s) need merge review."
warn " Review: node ~/.claude/adam/scripts/adam-upgrade.mjs --list"
warn " Accept: node ~/.claude/adam/scripts/adam-upgrade.mjs --accept <path>"
fi