KeiSeiKit-1.0/_primitives/kei-sleep-queue.sh
Parfii-bot a4e667de10 KeiSeiKit-public — clean state
Single-commit clean baseline after security scrub of niche-tells,
project codenames, internal jargon, and contributor-email leaks.

Contents:
- 100 Rust crates (_primitives/_rust/)
- 37 agent manifests (_manifests/) + generated specs (_generated/)
- 67 user-invocable skills (skills/)
- 33 hooks (hooks/)
- Composition blocks (_blocks/)
- Documentation (docs/, README.md)
- TS adapter packages (_ts_packages/)
- Assembler (_assembler/)
- Roles (_roles/)
- Templates (_templates/)
- Forgejo CI (.forgejo/)

Author: Denis Parfionovich <info@greendragon.info>

License: see LICENSE.
2026-05-01 12:09:03 +08:00

256 lines
8.8 KiB
Bash
Executable file

#!/usr/bin/env bash
# kei-sleep-queue.sh — v0.12.0 "sleep on it" queue CRUD helper.
# Commands: add / list / show / done / fail / purge.
# Env: KEI_MEMORY_REPO_PATH (sourced from ~/.claude/secrets/.env).
set -u
SECRETS_FILE="${HOME}/.claude/secrets/.env"
[ -f "$SECRETS_FILE" ] && [ -z "${KEI_MEMORY_REPO_PATH:-}" ] && \
. "$SECRETS_FILE" 2>/dev/null || true
REPO_PATH="${KEI_MEMORY_REPO_PATH:-}"
QUEUE_DIR="${REPO_PATH}/sleep-queue"
DONE_DIR="${REPO_PATH}/sleep-queue-done"
FAIL_DIR="${REPO_PATH}/sleep-queue-failed"
SYNC_SH="${HOME}/.claude/agents/_primitives/kei-sleep-sync.sh"
err() { printf 'kei-sleep-queue: %s\n' "$*" >&2; }
die() { err "$*"; exit 1; }
ensure_repo() {
[ -n "$REPO_PATH" ] || die "KEI_MEMORY_REPO_PATH not set (run /sleep-setup)"
[ -d "${REPO_PATH}/.git" ] || die "sync-repo not initialised at $REPO_PATH"
mkdir -p "$QUEUE_DIR" "$DONE_DIR" "$FAIL_DIR" 2>/dev/null || true
}
gen_uuid() {
if command -v uuidgen >/dev/null 2>&1; then uuidgen | tr 'A-Z' 'a-z'
else printf '%s-%s' "$(date -u +%s)" "${RANDOM}${RANDOM}"; fi
}
iso_utc() { date -u +%Y-%m-%dT%H:%M:%SZ; }
push_async() { [ -x "$SYNC_SH" ] && "$SYNC_SH" >/dev/null 2>&1 || true; }
# Find a queue file by uuid prefix in dir; echoes path or returns 1.
find_by_uuid() {
local uuid="$1" dir="$2" f
[ -d "$dir" ] || return 1
for f in "$dir/${uuid}-"*.md "$dir/${uuid}.md"; do
[ -f "$f" ] && { printf '%s\n' "$f"; return 0; }
done
return 1
}
# Extract a frontmatter field value (first match) from a file.
fm_field() { awk -F': ' -v k="^$2:" '$0 ~ k {print $2; exit}' "$1"; }
# Parse "<N>m" → N (minutes), or die with context.
parse_minutes() {
local raw="$1" label="$2" stripped="${1%m}"
case "$stripped" in ''|*[!0-9]*) die "bad $label: $raw (expected <N>m)" ;; esac
printf '%s\n' "$stripped"
}
# Priority defaults: TIME_BUDGET_MINUTES, CHECKPOINT_EVERY_MINUTES, MARATHON.
priority_defaults() {
case "$1" in
quick) printf '15 0 false\n' ;;
standard) printf '60 20 false\n' ;;
deep) printf '240 30 false\n' ;;
marathon) printf '480 30 true\n' ;;
weekly) printf '60 20 false\n' ;;
*) die "bad --priority: $1 (expected quick|standard|deep|marathon|weekly)" ;;
esac
}
# Validate add flags; sets ADD_* including time-budget / checkpoint / marathon.
parse_add_flags() {
ADD_TYPE=""; ADD_PRIORITY=""; ADD_FORMAT=""; ADD_PROMPT=""
ADD_TIME_BUDGET=""; ADD_CHECKPOINT=""; ADD_MARATHON=""; ADD_NO_TIMEOUT=0
while [ $# -gt 0 ]; do
case "$1" in
--type) ADD_TYPE="$2"; shift 2 ;;
--priority) ADD_PRIORITY="$2"; shift 2 ;;
--format) ADD_FORMAT="$2"; shift 2 ;;
--prompt-file) ADD_PROMPT="$2"; shift 2 ;;
--time-budget) ADD_TIME_BUDGET="$(parse_minutes "$2" --time-budget)"; shift 2 ;;
--checkpoint-every) ADD_CHECKPOINT="$(parse_minutes "$2" --checkpoint-every)"; shift 2 ;;
--no-timeout) ADD_NO_TIMEOUT=1; shift ;;
--marathon) ADD_MARATHON="true"; shift ;;
*) die "unknown flag: $1" ;;
esac
done
case "$ADD_TYPE" in deep|pipeline|pattern|compare|custom) ;; *) die "bad --type: $ADD_TYPE" ;; esac
case "$ADD_FORMAT" in md|adr|checklist|table) ;; *) die "bad --format: $ADD_FORMAT" ;; esac
[ -n "$ADD_PROMPT" ] && [ -f "$ADD_PROMPT" ] || die "missing --prompt-file"
resolve_priority_fields
}
# Apply priority defaults for any ADD_* fields that weren't overridden.
resolve_priority_fields() {
local defaults budget cp marathon
defaults="$(priority_defaults "$ADD_PRIORITY")"
budget="$(printf '%s' "$defaults" | awk '{print $1}')"
cp="$(printf '%s' "$defaults" | awk '{print $2}')"
marathon="$(printf '%s' "$defaults" | awk '{print $3}')"
[ -z "$ADD_TIME_BUDGET" ] && ADD_TIME_BUDGET="$budget"
[ -z "$ADD_CHECKPOINT" ] && ADD_CHECKPOINT="$cp"
[ -z "$ADD_MARATHON" ] && ADD_MARATHON="$marathon"
[ "$ADD_NO_TIMEOUT" = "1" ] && ADD_TIME_BUDGET="null"
if [ "$ADD_MARATHON" = "true" ] && [ "$ADD_PRIORITY" != "marathon" ]; then
err "warning: --marathon set but --priority=$ADD_PRIORITY (expected marathon)"
fi
}
cmd_add() {
parse_add_flags "$@"
ensure_repo
local uuid ts file
uuid="$(gen_uuid)"
ts="$(iso_utc)"
file="${QUEUE_DIR}/${uuid}-$(date -u +%s).md"
{
printf -- '---\n'
printf -- 'uuid: %s\n' "$uuid"
printf -- 'submitted_at: %s\n' "$ts"
printf -- 'type: %s\n' "$ADD_TYPE"
printf -- 'priority: %s\n' "$ADD_PRIORITY"
printf -- 'format: %s\n' "$ADD_FORMAT"
printf -- 'time_budget_minutes: %s\n' "$ADD_TIME_BUDGET"
printf -- 'checkpoint_every_minutes: %s\n' "$ADD_CHECKPOINT"
printf -- 'marathon: %s\n' "$ADD_MARATHON"
printf -- 'status: pending\n---\n\n'
cat "$ADD_PROMPT"
printf '\n'
} > "$file" || die "write failed: $file"
printf '%s\n%s\n' "$uuid" "$file"
push_async
}
cmd_list() {
local filter="pending" dir
[ $# -gt 0 ] && case "$1" in
--pending) filter="pending" ;;
--done) filter="done" ;;
--failed) filter="failed" ;;
*) die "unknown filter: $1" ;;
esac
ensure_repo
case "$filter" in
pending) dir="$QUEUE_DIR" ;; done) dir="$DONE_DIR" ;; failed) dir="$FAIL_DIR" ;;
esac
printf '%-36s %-10s %-8s %-9s %s\n' UUID SUBMITTED TYPE PRIORITY FILE
local f u s t p
for f in "$dir"/*.md; do
[ -f "$f" ] || continue
u="$(fm_field "$f" uuid)"
s="$(fm_field "$f" submitted_at | cut -c1-10)"
t="$(fm_field "$f" type)"
p="$(fm_field "$f" priority)"
printf '%-36s %-10s %-8s %-9s %s\n' "${u:--}" "${s:--}" "${t:--}" "${p:--}" "$f"
done
}
cmd_show() {
[ $# -ge 1 ] || die "usage: show <uuid>"
ensure_repo
local uuid="$1" f dir
for dir in "$QUEUE_DIR" "$DONE_DIR" "$FAIL_DIR"; do
f="$(find_by_uuid "$uuid" "$dir" 2>/dev/null)" && { cat "$f"; return 0; }
done
die "uuid not found: $uuid"
}
cmd_done() {
[ $# -ge 1 ] || die "usage: done <uuid>"
ensure_repo
local src dest uuid="$1"
src="$(find_by_uuid "$uuid" "$QUEUE_DIR")" || die "pending uuid not found: $uuid"
dest="${DONE_DIR}/${uuid}.md"
sed 's/^status: pending$/status: done/' "$src" > "$dest" || die "write failed: $dest"
rm -f "$src"
printf 'moved: %s -> %s\n' "$src" "$dest"
push_async
}
cmd_fail() {
local uuid="" reason=""
while [ $# -gt 0 ]; do
case "$1" in
--reason) reason="$2"; shift 2 ;;
*) [ -z "$uuid" ] && { uuid="$1"; shift; } || die "unknown arg: $1" ;;
esac
done
[ -n "$uuid" ] || die "usage: fail <uuid> --reason <text>"
ensure_repo
local src dest
src="$(find_by_uuid "$uuid" "$QUEUE_DIR")" || die "pending uuid not found: $uuid"
dest="${FAIL_DIR}/${uuid}.md"
{
sed 's/^status: pending$/status: failed/' "$src"
printf '\n---\n## Failure reason\n\n%s\n' "${reason:-(no reason given)}"
} > "$dest" || die "write failed: $dest"
rm -f "$src"
printf 'moved: %s -> %s\n' "$src" "$dest"
push_async
}
cmd_purge() {
local days=""
while [ $# -gt 0 ]; do
case "$1" in
--older-than) days="${2%d}"; shift 2 ;;
*) die "unknown flag: $1" ;;
esac
done
case "$days" in ''|*[!0-9]*) die "--older-than <N>d required (N integer)" ;; esac
ensure_repo
local removed=0 f dir
for dir in "$DONE_DIR" "$FAIL_DIR"; do
while IFS= read -r f; do
rm -f "$f" && removed=$((removed + 1))
done < <(find "$dir" -maxdepth 1 -type f -name '*.md' -mtime "+$days" 2>/dev/null)
done
printf 'purged %d file(s) older than %sd\n' "$removed" "$days"
push_async
}
usage() {
cat >&2 <<'EOF'
kei-sleep-queue.sh — v0.12 sleep-on-it queue helper
add --type <deep|pipeline|pattern|compare|custom>
--priority <quick|standard|deep|marathon|weekly>
--format <md|adr|checklist|table>
--prompt-file <path>
[--time-budget <N>m] override minutes from priority default
[--checkpoint-every <M>m] override partial-result cadence
[--no-timeout] time_budget_minutes: null (run until done)
[--marathon] explicit marathon flag
list [--pending|--done|--failed]
show <uuid>
done <uuid>
fail <uuid> --reason <text>
purge --older-than <N>d
Env: KEI_MEMORY_REPO_PATH (required)
EOF
exit 1
}
main() {
[ $# -ge 1 ] || usage
local sub="$1"; shift
case "$sub" in
add) cmd_add "$@" ;;
list) cmd_list "$@" ;;
show) cmd_show "$@" ;;
done) cmd_done "$@" ;;
fail) cmd_fail "$@" ;;
purge) cmd_purge "$@" ;;
-h|--help|help) usage ;;
*) die "unknown command: $sub" ;;
esac
}
main "$@"