session-status.sh

Session status report

Tier 2engineeringPrints, does not block

Report issues that cannot block offline work. Flag generated files that are older than their sources.

Add to your project
Add "Session status report" from ntent to this repo.

Fetch https://ntent.app/r/f/session-status as plain text. Write it verbatim to scripts/session-status.sh. Then make it executable.

Then check it: It prints when someone opens the repo, which is the moment they can act on it. Touch a token source without rebuilding and it tells you that you are looking at the previous build.

Reads https://ntent.app/r/f/session-status

Read the code
session-status.shscripts/session-status.sh · make it executable
156 lines
#!/usr/bin/env bash
#
# Session start banner. Runs when a coding session opens, and by hand any time.
#
# Two jobs. It orients whoever just sat down: are you behind, what is running,
# where do you pick up. And it is where the checks that cannot be commit gates
# get their audience, because a check that needs credentials or the network has
# to nag somewhere, and the moment someone opens the repo is the moment they can
# act on it.
#
# THREE RULES.
#
# 1. Never block on the network. The git sync line reads local refs only, then
#    fetches in the background so the NEXT session is accurate. A banner that
#    takes four seconds is a banner people disable.
#
# 2. Silent unless it matters. Lines that have nothing to say print nothing. A
#    banner that always prints eight lines gets skimmed and then ignored, which
#    means the one time it says something urgent, nobody reads it.
#
# 3. Under a second, always. Everything here is a local file read or a git
#    plumbing command.
#
# Wire it into your agent's session-start hook, and add it to the README so
# people can run it directly:  bash scripts/session-status.sh

set -uo pipefail
cd "$(git rev-parse --show-toplevel 2>/dev/null || echo .)" || exit 0

# ---------------------------------------------------------------- CONFIG ---
# Build outputs that are generated and not committed, and the source that
# generates them.
#
# The missing case is the loud one and doctor.sh covers it too. STALENESS is the
# half only this script can catch: the file is there, the app starts, everything
# renders, and you are looking at the previous build. Nothing anywhere says so.
# You debug a change you are not running. It costs an hour every time.
#
# One entry per line, three fields separated by | :
#
#   generated output | the source directory that produces it | the command that rebuilds it
#
# Point the second field at the source directory, not at the repo root. It is
# walked on every session start and rule 3 is one second for the whole banner.
#
# An empty list is fine. Nothing here prints when there is nothing in it.
GENERATED=(
  # "src/styles/tokens.css|tokens/src|pnpm tokens:build"
  # "src/generated/schema.ts|prisma|pnpm codegen"
)

# ------------------------------------------------------- 1. git position ---
if git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
  branch=$(git branch --show-current 2>/dev/null || echo detached)
  if upstream=$(git rev-parse --abbrev-ref --symbolic-full-name '@{u}' 2>/dev/null); then
    behind=$(git rev-list --count "HEAD..$upstream" 2>/dev/null || echo 0)
    ahead=$(git rev-list --count "$upstream..HEAD" 2>/dev/null || echo 0)
    if   [ "$behind" -gt 0 ] && [ "$ahead" -gt 0 ]; then
      echo "git:   diverged from $upstream. $behind behind, $ahead ahead"
    elif [ "$behind" -gt 0 ]; then
      echo "git:   $behind behind $upstream. Pull before you start"
    elif [ "$ahead" -gt 0 ]; then
      echo "git:   $ahead unpushed commit(s) on $branch"
    fi
    # Silent when in sync. Rule 2.
  else
    echo "git:   $branch has no upstream branch set"
  fi
  dirty=$(git status --porcelain 2>/dev/null | wc -l | tr -d ' ')
  [ "$dirty" -gt 0 ] && echo "git:   $dirty uncommitted change(s)"

  # Refresh origin for next time. Never blocks.
  ( nohup git fetch --quiet >/dev/null 2>&1 & ) >/dev/null 2>&1
fi

# ----------------------------------------------------- 2. setup problems ---
# The two that silently disable everything else, so they are worth one line each.

hooks="$(git config core.hooksPath 2>/dev/null)"
if [ "$hooks" != "scripts/git-hooks" ]; then
  echo "setup: git hooks are NOT wired, so no checks run on commit."
  echo "       fix: git config core.hooksPath scripts/git-hooks"
fi

if [ -f .env.example ] && [ ! -f .env.local ]; then
  echo "setup: no .env.local. Run: cp .env.example .env.local"
fi

# Generated artefacts: missing, or older than the source that produces them.
if [ "${#GENERATED[@]}" -gt 0 ]; then
  for entry in "${GENERATED[@]}"; do
    IFS='|' read -r gen_out gen_src gen_cmd <<EOF
$entry
EOF
    if [ ! -f "$gen_out" ]; then
      echo "setup: $gen_out is not built. The app will start and look BROKEN rather"
      echo "       than unbuilt, so you will debug the wrong thing. fix: $gen_cmd"
    elif [ -n "$(find "$gen_src" -newer "$gen_out" -type f -print -quit 2>/dev/null)" ]; then
      # -print -quit stops at the first hit, so this stays a partial walk however
      # large the source tree is.
      echo "setup: a file in $gen_src is newer than $gen_out. You are looking at the"
      echo "       PREVIOUS build and nothing else will tell you. fix: $gen_cmd"
    fi
  done
fi

# ---------------------------------------------------------- 3. the nags ----
# Checks that cannot be commit gates because the person committing may not be
# able to satisfy them. See chapter 2 on the gate-versus-nag distinction.

# Contract coverage, if there is a contract. Offline and instant.
if [ -f contract.json ] && [ -f scripts/check-contract-coverage.mjs ]; then
  cov=$(node scripts/check-contract-coverage.mjs 2>/dev/null | grep -o 'Overall type-layer coverage: [0-9]*%' || true)
  [ -n "$cov" ] && echo "build: ${cov#Overall type-layer coverage: } of the contract is implemented  (pnpm coverage for the breakdown)"
fi

# Stale build-log stubs. A queue that grows forever is a queue nobody drains,
# so say the number out loud once it stops being small.
if [ -f docs/build-log/_pending.md ]; then
  pending=$(grep -c '^- \[ \]' docs/build-log/_pending.md 2>/dev/null || echo 0)
  [ "$pending" -gt 10 ] && echo "log:   $pending undrained build-log stub(s). Worth 10 minutes before they go cold"
fi

# Rapid mode left on. It is meant to be temporary, and forgetting it on is how
# the feed quietly stops.
if [ "$(git config --bool myproject.rapid 2>/dev/null)" = "true" ]; then
  spooled=$(wc -l < "$(git rev-parse --git-dir)/rapid-skipped-stubs" 2>/dev/null | tr -d ' ' || echo 0)
  echo "log:   rapid mode is ON, $spooled stub(s) spooled. Drain and: git config --unset myproject.rapid"
fi

# Flags past their removal date. Cheap to compute, and flag debt is the quiet
# killer of a long-lived SaaS codebase.
if [ -f src/lib/flags.json ]; then
  today=$(date +%Y-%m-%d)
  expired=$(node -e "
    const f=require('./src/lib/flags.json');
    const n=Object.entries(f).filter(([,v])=>v.removeBy && v.removeBy < '$today').map(([k])=>k);
    if(n.length) console.log(n.length + ' flag(s) past removal date: ' + n.join(', '));
  " 2>/dev/null || true)
  [ -n "$expired" ] && echo "flags: $expired"
fi

# ------------------------------------------------------- 4. how to run -----
echo "run:   pnpm dev      the app          -> http://localhost:3000"
echo "       pnpm check    every gate, the way CI runs them"
echo "       pnpm doctor   what your machine is missing"

# ------------------------------------------------------- 5. where to pick --
# Print the resume pointer, if there is one. Everything between the markers goes
# into the session verbatim, so the file has a hard size cap. See chapter 7.
if [ -f docs/PROGRESS.md ]; then
  awk '/<!-- RESUME:START -->/{flag=1;next}/<!-- RESUME:END -->/{flag=0}flag' docs/PROGRESS.md | head -40
fi

exit 0

Success check: It prints when someone opens the repo, which is the moment they can act on it. Touch a token source without rebuilding and it tells you that you are looking at the previous build.

Included in