Some checks are pending
CI (Forgejo Actions — self-hosted runner on Mac, host mode) / preflight (push) Waiting to run
CI (Forgejo Actions — self-hosted runner on Mac, host mode) / vps-smoke (push) Waiting to run
CI (Forgejo Actions — self-hosted runner on Mac, host mode) / rust-primitives (map[crates:frustration-matrix,kei-frustration-loop,kei-skill-importer,kei-projects-index,kei-projects-watcher,kei-gdrive-import,kei-leak-matrix,kei-skills,kei-gateway,kei-cron-scheduler,kei-export-trajectories,kei-backend-daytona,kei-d… (push) Blocked by required conditions
CI (Forgejo Actions — self-hosted runner on Mac, host mode) / rust-primitives (map[crates:kei-compute-baremetal,kei-compute-vultr,kei-compute-linode,kei-compute-digitalocean,kei-svc-systemd,kei-llm-bridge-mlx name:hosted-sleep-compute]) (push) Blocked by required conditions
CI (Forgejo Actions — self-hosted runner on Mac, host mode) / rust-primitives (map[crates:kei-diff,kei-scheduler,kei-watch,kei-prune,kei-discover,kei-brain-view,kei-hibernate,kei-ledger-sign,kei-fork name:wave13-15]) (push) Blocked by required conditions
CI (Forgejo Actions — self-hosted runner on Mac, host mode) / rust-primitives (map[crates:kei-git-gitea,kei-git-forgejo,kei-git-gitlab,kei-git-bitbucket,kei-memory-sled,kei-memory-redis,kei-memory-postgres,kei-memory-sqlite,kei-auth-google,kei-auth-apple,kei-auth-magiclink,kei-auth-webauthn,kei-notify-slack,kei-n… (push) Blocked by required conditions
CI (Forgejo Actions — self-hosted runner on Mac, host mode) / rust-primitives (map[crates:kei-ledger,kei-migrate,kei-changelog,kei-memory,kei-store,kei-conflict-scan,kei-refactor-engine,kei-graph-check,kei-shared,kei-dna-index,kei-pet name:core]) (push) Blocked by required conditions
CI (Forgejo Actions — self-hosted runner on Mac, host mode) / rust-primitives (map[crates:kei-machine-probe,kei-llm-ollama,kei-llm-llamacpp,kei-llm-mlx,kei-llm-router,kei-model name:llm-stack]) (push) Blocked by required conditions
CI (Forgejo Actions — self-hosted runner on Mac, host mode) / rust-primitives (map[crates:kei-router,kei-sage,kei-task,kei-chat-store,kei-crossdomain,kei-search-core,kei-content-store,kei-social-store,kei-curator,kei-auth,kei-artifact name:mcp-lbm]) (push) Blocked by required conditions
CI (Forgejo Actions — self-hosted runner on Mac, host mode) / rust-primitives (map[crates:keisei,kei-forge,kei-runtime,kei-runtime-core,kei-atom-discovery,kei-agent-runtime,kei-capability,kei-provision,kei-entity-store,kei-pipe,kei-cache,kei-spawn,kei-replay name:atom-substrate]) (push) Blocked by required conditions
web-install.sh tees stdout to a logfile (exec > >(tee) 2>&1), so -t 1 is false even in an interactive curl|bash. The /dev/tty fix reattached stdin but the wizard gates required BOTH -t 0 and -t 1, so onboarding (language select) and bootstrap's profile wizard were silently skipped on the primary install path. Prompts go to stderr and read from stdin — interactive stdin is the only real requirement (already the proven pattern in lib-plan.sh confirm screen). Gates now require interactive stdin only: bootstrap.sh profile wizard lib-onboarding.sh onboarding_should_run + preflight-continue prompt lib-preflight.sh CLI-install offer prompt lib-hooks.sh activate-hooks prompt Non-interactive (CI / </dev/null / no /dev/tty) still skips — verified. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
145 lines
5.4 KiB
Bash
145 lines
5.4 KiB
Bash
# shellcheck shell=bash
|
|
# lib-hooks.sh — hook file copy + settings.json jq-merge.
|
|
#
|
|
# Hooks are logic (not config) → always refreshed, every install.
|
|
# settings.json merge is idempotent: it groups by matcher and unions .hooks
|
|
# by unique command so repeated runs never duplicate entries.
|
|
#
|
|
# Requires: say / warn / err from lib-log.sh.
|
|
# Requires: backup_file from lib-backup.sh.
|
|
# Reads globals: $KIT_DIR, $HOOKS_DIR, $HOME_DIR.
|
|
|
|
# Copy every *.sh hook from the kit into $HOOKS_DIR, +x, with per-file backup.
|
|
# Also copies hooks/_lib/*.sh → $HOOKS_DIR/_lib/ (v0.17 gate-extract). The
|
|
# hooks dot-source "$(dirname "$0")/_lib/gate.sh" — the _lib/ sub-tree MUST
|
|
# land alongside the hook scripts or the gate becomes a no-op (fail-open).
|
|
install_hooks() {
|
|
say "copying hooks -> $HOOKS_DIR/"
|
|
local hook_count=0 hook_src h
|
|
for hook_src in "$KIT_DIR/hooks/"*.sh; do
|
|
[ -f "$hook_src" ] || continue
|
|
h="$(basename "$hook_src")"
|
|
backup_file "$HOOKS_DIR/$h"
|
|
cp -f "$hook_src" "$HOOKS_DIR/$h"
|
|
chmod +x "$HOOKS_DIR/$h"
|
|
hook_count=$((hook_count+1))
|
|
done
|
|
say " installed $hook_count hook(s)"
|
|
|
|
# v0.17 — shared hook library (gate.sh + test-gate.sh)
|
|
if [ -d "$KIT_DIR/hooks/_lib" ]; then
|
|
mkdir -p "$HOOKS_DIR/_lib"
|
|
local lib_count=0 lib_src lib_name
|
|
for lib_src in "$KIT_DIR/hooks/_lib/"*.sh; do
|
|
[ -f "$lib_src" ] || continue
|
|
lib_name="$(basename "$lib_src")"
|
|
cp -f "$lib_src" "$HOOKS_DIR/_lib/$lib_name"
|
|
chmod +x "$HOOKS_DIR/_lib/$lib_name"
|
|
lib_count=$((lib_count+1))
|
|
done
|
|
say " installed $lib_count hook library file(s) -> $HOOKS_DIR/_lib/"
|
|
fi
|
|
}
|
|
|
|
# Merge settings-snippet.json into ~/.claude/settings.json non-interactively
|
|
# via jq. On first run (no settings.json) we strip _comment and drop in the
|
|
# snippet verbatim. On subsequent runs we group by matcher and dedupe .hooks
|
|
# by command so re-runs are true no-ops.
|
|
# jq-merge snippet into existing target. group_by matcher + dedup by command
|
|
# so re-runs are no-ops. Args: $1=snippet, $2=target.
|
|
_jq_merge_hooks() {
|
|
local snippet="$1" target="$2" tmp
|
|
tmp="$(mktemp "$target.XXXXXX")"
|
|
jq --slurpfile snip "$snippet" '
|
|
# Normalize a command path: expand leading ~/ to $HOME so tilde and
|
|
# absolute forms compare equal (prevents duplicate hook registration).
|
|
def norm: if startswith("~/") then env.HOME + .[1:] else . end;
|
|
|
|
. as $orig
|
|
| ($snip[0] | del(._comment)) as $add
|
|
| reduce ($add.hooks | keys[]) as $phase ($orig;
|
|
.hooks[$phase] = (
|
|
((.hooks[$phase] // []) + ($add.hooks[$phase] // []))
|
|
| group_by(.matcher)
|
|
| map(
|
|
.[0].matcher as $m
|
|
| {
|
|
matcher: $m,
|
|
hooks: (
|
|
map(.hooks // []) | add
|
|
# Reduce into object keyed by normalised command.
|
|
# Last entry wins → snippet (appended last) overrides
|
|
# existing on collision, preserving all extra fields.
|
|
| reduce .[] as $h (
|
|
{};
|
|
. + { (($h.command // "") | norm): $h }
|
|
)
|
|
| [.[]]
|
|
)
|
|
}
|
|
)
|
|
)
|
|
)
|
|
# statusLine (KeiSei tamagotchi): set ONLY when the target has none.
|
|
# Never clobber an existing statusLine. Fresh-install path drops the
|
|
# snippet verbatim, so this only matters when merging into a
|
|
# pre-existing settings.json.
|
|
| if (.statusLine // null) == null and ($add.statusLine // null) != null
|
|
then .statusLine = $add.statusLine
|
|
else . end
|
|
' "$target" > "$tmp"
|
|
if [ -s "$tmp" ] && jq -e . "$tmp" >/dev/null 2>&1; then
|
|
mv "$tmp" "$target"
|
|
say "merged hooks into $target (idempotent)"
|
|
else
|
|
rm -f "$tmp"
|
|
err "jq-merge produced invalid output; $target unchanged"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
activate_hooks() {
|
|
local snippet="$KIT_DIR/settings-snippet.json"
|
|
local target="$HOME_DIR/.claude/settings.json"
|
|
[ -f "$snippet" ] || { warn "no snippet at $snippet"; return 0; }
|
|
if [ ! -f "$target" ]; then
|
|
local tmp
|
|
tmp="$(mktemp "$target.XXXXXX")"
|
|
jq 'del(._comment)' "$snippet" > "$tmp"
|
|
mv "$tmp" "$target"
|
|
say "created $target from snippet (no prior settings.json)"
|
|
return 0
|
|
fi
|
|
backup_file "$target"
|
|
_jq_merge_hooks "$snippet" "$target"
|
|
}
|
|
|
|
# Flag-or-prompt dispatcher, mirroring the v0.15 behavior:
|
|
# --activate-hooks → always activate, no prompt
|
|
# no existing settings.json → activate silently (drop in snippet)
|
|
# TTY stdin+stdout → interactive [y/N] prompt
|
|
# otherwise → skip (manual-merge hint printed by summary)
|
|
# Sets global DID_ACTIVATE=1 when activation ran + succeeded.
|
|
maybe_activate_hooks() {
|
|
local settings_file="$HOME_DIR/.claude/settings.json"
|
|
DID_ACTIVATE=0
|
|
if [ "$ACTIVATE_HOOKS" = "1" ]; then
|
|
say "activating hooks (--activate-hooks)"
|
|
activate_hooks && DID_ACTIVATE=1
|
|
elif [ ! -f "$settings_file" ]; then
|
|
say "no existing settings.json; installing snippet"
|
|
activate_hooks && DID_ACTIVATE=1
|
|
elif [ -t 0 ]; then # stdin-only: stdout may be tee'd in curl|bash
|
|
if [ "$COLOR" = "1" ]; then
|
|
printf '\033[1;36m[install]\033[0m activate hooks now? [y/N] '
|
|
else
|
|
printf '[install] activate hooks now? [y/N] '
|
|
fi
|
|
local reply
|
|
read -r reply
|
|
case "$reply" in
|
|
y|Y|yes|YES) activate_hooks && DID_ACTIVATE=1 ;;
|
|
*) say "skipping hook activation" ;;
|
|
esac
|
|
fi
|
|
}
|