← Files NightshiftARCHIVED FILE

runtime/punch-list.sh

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

↓ Download file

#!/usr/bin/env bash
# punch-list.sh — the gates block and one item, printed exactly as the owner wrote them.
#
#   punch-list.sh --project <ws> next        the gates block, then the first still-open item
#   punch-list.sh --project <ws> item <item> the gates block, then that item, named by its
#                                            number, its id, or its whole label
#
# The `## Gates` block may legitimately change mid-shift, so an item needs it fresh — and reading
# the whole punch list to see one block is thousands of tokens per item on a long list. This prints
# the two things an item actually needs and nothing else.
#
# It is a reader and only a reader. Nothing here rewrites, reorders, renumbers or summarises an
# item: what comes out is the file's own text, byte for byte, so a model working from it is
# working from the contract rather than from someone's précis of it.
#
# An item is one top-level checkbox line plus the indented lines that follow it, up to the next
# top-level line — the same bounded rule the gate and Status already use for counting boxes, not a
# Markdown parser. Fenced code and nested lists inside an item are indented, so they come through
# whole.
#
# Exit: 0 printed, or `none` when nothing is open · 1 usage · 2 refused
set -u

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

PROJECT="${CLAUDE_PROJECT_DIR:-${CODEX_PROJECT_DIR:-$PWD}}"
VERB=""
WANT=""
while [ $# -gt 0 ]; do
  case "$1" in
    --project)
      [ $# -ge 2 ] || { printf 'punch-list: --project needs a value\n' >&2; exit 1; }
      PROJECT="$2"
      shift 2
      ;;
    next)
      VERB=next
      shift
      ;;
    item)
      [ $# -ge 2 ] || { printf 'punch-list: item needs a number, an id, or a label\n' >&2; exit 1; }
      VERB=item
      WANT="$2"
      shift 2
      ;;
    -h | --help)
      awk 'NR == 1 { next } !/^#/ { exit } { sub(/^# ?/, ""); print }' "$0"
      exit 1
      ;;
    *) printf 'punch-list: unknown argument: %s\n' "$1" >&2; exit 1 ;;
  esac
done
[ -n "$VERB" ] || { printf 'punch-list: usage: punch-list.sh [--project DIR] next|item <number|id|label>\n' >&2; exit 1; }

HOST="$(cd -P "$PROJECT" 2>/dev/null && pwd)" || {
  printf 'punch-list: 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 'punch-list: invalid .nightshift-link — Nightshift will not guess a workspace\n' >&2
    exit 2
  }
fi
declare PUNCH
ns_layout_set PUNCH "$WORKSPACE/.nightshift" punch-list
if [ ! -f "$PUNCH" ] || [ -L "$PUNCH" ]; then
  printf 'punch-list: no punch list at %s\n' "$PUNCH" >&2
  exit 2
fi

# The gates block, verbatim: everything from its heading to the next top-level heading. The owner
# may change it mid-shift by design, which is the whole reason this is printed every time.
ns_punch_gates "$PUNCH"

if [ "$VERB" = next ]; then
  body="$(ns_punch_item "$PUNCH" "")"
else
  body="$(ns_punch_item "$PUNCH" "$WANT")"
fi
if [ -z "$body" ]; then
  if [ "$VERB" = item ]; then
    printf 'punch-list: no item %s in %s\n' "$WANT" "$PUNCH" >&2
    exit 2
  fi
  printf 'none\n'
  exit 0
fi
printf '%s\n' "$body"
exit 0

SHA-256: f93a97ce925d4a070987884418cf34b82930ad4bed7b760ff3194a8f1290deb7