← Files NightshiftARCHIVED FILE

runtime/park-needs.sh

4.36 KB · Oct 3, 2026 · 06:31 UTC

↓ Download file

#!/usr/bin/env bash
# park-needs.sh — park the items whose elevation the owner has not granted.
#
#   park-needs.sh --project DIR
#
# Start asks nothing. It arms, parks every item the permission preflight found a gap for, and
# works the rest. This helper is that parking step, so it is mechanical rather than remembered:
# one entry per item and category, appended to parking-lot.md, and running it again adds nothing.
# It reads the preflight's own report, so the guard, the preflight and the parking entry can
# never name different categories.
#
# Prints one `parked <category>: <title>` line per entry added, then `park-needs: added N`.
# Exit: 0 ok · 1 usage · 2 no JSON parser, or the parking lot cannot be written
set -u

_here="${BASH_SOURCE[0]%/*}"
[ "$_here" != "${BASH_SOURCE[0]}" ] || _here=.
# shellcheck source=plugins/nightshift/lib/lib.sh
. "$_here/../lib/lib.sh"

PREFLIGHT="$_here/preflight-needs.sh"
TEMPLATE="$_here/../skills/nightshift/references/templates/parking-lot.md"

usage() {
  awk 'NR == 1 { next } !/^#/ { exit } { sub(/^# ?/, ""); print }' "$0" >&2
  exit 1
}

PROJECT="${CLAUDE_PROJECT_DIR:-${CODEX_PROJECT_DIR:-$PWD}}"

while [ $# -gt 0 ]; do
  case "$1" in
    --project)
      [ $# -ge 2 ] || usage
      PROJECT="$2"
      shift 2
      ;;
    -h | --help) usage ;;
    *)
      printf 'park-needs: unknown argument: %s\n' "$1" >&2
      usage
      ;;
  esac
done

HOST="$(cd -P "$PROJECT" 2>/dev/null && pwd)" || {
  printf 'park-needs: cannot cd to %s\n' "$PROJECT" >&2
  exit 1
}
WORKSPACE="$HOST"
if [ -e "$HOST/.nightshift-link" ] || [ -L "$HOST/.nightshift-link" ]; then
  WORKSPACE="$(ns_workspace_root "$HOST" 2>/dev/null)" || {
    printf 'park-needs: invalid .nightshift-link — Nightshift will not guess a workspace\n' >&2
    exit 1
  }
fi
NS="$WORKSPACE/.nightshift"
declare LOT
ns_layout_set LOT "$NS" parking-lot

no_parser() {
  printf 'park-needs: JSON parser unavailable; write the parking-lot row in the skill\n' >&2
  exit 2
}

cannot_write() {
  printf 'park-needs: cannot write %s\n' "$LOT" >&2
  exit 2
}

ns_policy_json_tool >/dev/null || no_parser
[ -d "$NS" ] || {
  printf 'park-needs: no .nightshift/ at %s — run setup first\n' "$WORKSPACE" >&2
  exit 2
}

TAB="$NS_POLICY_TAB"
TMPD="$(mktemp -d)" || {
  printf 'park-needs: cannot create a temporary directory\n' >&2
  exit 2
}
trap 'rm -rf "$TMPD"' EXIT

bash "$PREFLIGHT" --project "$WORKSPACE" --json >"$TMPD/preflight.json" || no_parser

GAPS_PY='
import json, sys

for gap in json.load(open(sys.argv[1]))["gaps"]:
    sys.stdout.write("%s\t%s\n" % (gap["category"], gap["title"]))
'

if [ "$(ns_policy_json_tool)" = jq ]; then
  jq -r '.gaps[] | .category + "\t" + .title' "$TMPD/preflight.json" >"$TMPD/gaps" || no_parser
else
  python3 -c "$GAPS_PY" "$TMPD/preflight.json" >"$TMPD/gaps" || no_parser
fi

# The entry the owner reads over coffee: what is missing, which item it holds, and the default
# taken so the night could continue. It is a `- ` bullet, the shape Archive files once the owner
# answers it. The text is also the idempotency key — an entry already in the file, as a bullet or
# as the bare line an earlier release wrote, is the same work already parked.
entry() { # <category> <title>
  printf -- '- **needs allowance: %s** — item "%s" needs the %s elevation category, which is denied for this shift. Default: parked, worked last if the owner allows it before then.' \
    "$1" "$2" "$1"
}

ADDED=0
: >"$TMPD/new"
: >"$TMPD/said"
while IFS="$TAB" read -r category title; do
  [ -n "$category" ] || continue
  line="$(entry "$category" "$title")"
  if [ -f "$LOT" ] && grep -qxF -e "$line" -e "${line#- }" "$LOT"; then
    continue
  fi
  grep -qxF "$line" "$TMPD/new" 2>/dev/null && continue
  printf '%s\n' "$line" >>"$TMPD/new"
  printf 'parked %s: %s\n' "$category" "$title" >>"$TMPD/said"
  ADDED=$((ADDED + 1))
done <"$TMPD/gaps"

if [ "$ADDED" -gt 0 ]; then
  if [ ! -f "$LOT" ]; then
    if [ -f "$TEMPLATE" ]; then
      cp "$TEMPLATE" "$LOT" || cannot_write
    else
      printf '# Parking Lot\n\n---\n\n(empty)\n' >"$LOT" || cannot_write
    fi
  fi
  {
    cat "$LOT"
    while IFS= read -r line; do
      [ -n "$line" ] || continue
      printf '\n%s\n' "$line"
    done <"$TMPD/new"
  } >"$LOT.tmp.$$" || {
    rm -f "$LOT.tmp.$$"
    cannot_write
  }
  mv "$LOT.tmp.$$" "$LOT" || {
    rm -f "$LOT.tmp.$$"
    cannot_write
  }
  cat "$TMPD/said"
fi
printf 'park-needs: added %s\n' "$ADDED"
exit 0

SHA-256: a467e8ed49bfb5cbb917241011fb51f5d09782b49d77a56e6455f3df93cc9957