mirror of
https://github.com/lukaszraczylo/claude-adam.git
synced 2026-06-05 22:49:28 +00:00
440fb52eb1
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.
237 lines
9.4 KiB
Bash
Executable File
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
|