npm package: @snevins/nudge
Description: Sequential reminder queue engine
Author: snevins-bot
Latest version: 0.2.1

# nudge

`nudge` is a tiny sequential reminder queue engine. It creates UUID queues, checks them (exit codes for gating), advances them (`next`), and auto-cleans up completed queues (default: archive).

## Install

Requires Node `22.13.0+`.

```bash
pnpm add -g @snevins/nudge
```

## Quick start (wrapper-style)

```bash
ID="$(nudge queue "Run migrations" "Update changelog")"

nudge "$ID"          # exit 1 until complete
nudge next "$ID"
nudge next "$ID"     # completes + archives by default
nudge "$ID"          # now exists:false (and archived:true)
```

## Storage

`nudge` picks a root directory automatically:

- inside a git repo: `.git/nudge/`
- outside a git repo: `./.nudge/`

If a git repo is detected but `.git/nudge/` is not writable, `nudge` falls back to `./.nudge/` at the repo root.

Layout:

```text
NUDGE_ROOT/
  nudge.sqlite
```

## Commands

### `nudge version`

Print the current installed `nudge` version.

```bash
nudge version
nudge --version
```

### `nudge queue <reminder...>`

Create a new queue (UUID v4 generated).

```bash
nudge queue "Run migrations" "Update changelog"
nudge queue --json "Run migrations"
nudge queue -- "-starts-with-dash"
```

### `nudge <queueId>`

Check a queue.

- exit `1` when pending reminders exist
- exit `0` when missing or complete

```bash
nudge <queueId>
nudge <queueId> --json
```

### `nudge next <queueId>`

Acknowledge the next unacked item and advance cursor sequentially.

On completion, cleanup runs automatically (default: `archive`).

If the queue is already complete but still marked active in the SQLite store (e.g., a crash between final write and cleanup), calling `nudge next <queueId>` again applies the cleanup policy (unless `--cleanup keep`).

Archiving is idempotent: once a completed queue is marked archived in the SQLite store, later `check` / `next` calls continue returning `archived:true`.

In human mode (no `--json`):

- prints the acked reminder text when an item is acknowledged
- exits `1` when it advances the queue (an item was acknowledged)
- exits `0` when missing or already complete (no-op)
- prints nothing when missing or already complete

```bash
nudge next <queueId>
nudge next <queueId> --json
nudge next <queueId> --cleanup archive|delete|keep
```

### `nudge clear <queueId>`

Delete active queue state (idempotent; does not purge archive).

```bash
nudge clear <queueId>
nudge clear <queueId> --json
```

### `nudge --all`

List active queues.

- exit `1` when any pending exists
- exit `0` when none are pending

```bash
nudge --all
nudge --all --json
```

Ordering: `--all` output is sorted by `queueId` ascending for stability.

## Exit codes (gating API)

- `0`: missing or complete (`nudge <queueId>`); none pending (`nudge --all`); `next` no-op (missing/already complete); success (`queue`, `clear`)
- `1`: pending reminders exist (`nudge <queueId>` / `nudge --all`); `next` advanced (acked an item)
- `2`: error (invalid UUID, lock timeout, IO/corrupt state)

## JSON output

`--json` makes stdout stable JSON (no extra stdout text). By default:

- `queue`, `check`, `next`: full snapshot including `items`
- `--all`: summary only (no `items`)

All timestamp fields (`createdAt`, `updatedAt`, `ackAt`) are RFC3339 strings (e.g. `2026-03-04T20:00:00.000Z`).

### Full snapshot schema (`queue`, `check`, `next`)

```json
{
  "queueId": "uuid",
  "createdAt": "2026-03-04T20:00:00.000Z",
  "updatedAt": "2026-03-04T20:00:00.000Z",
  "cursor": 0,
  "done": 0,
  "total": 2,
  "complete": false,
  "next": "Run migrations",
  "items": [
    { "index": 0, "text": "Run migrations", "createdAt": "2026-03-04T20:00:00.000Z", "ackAt": null },
    { "index": 1, "text": "Update changelog", "createdAt": "2026-03-04T20:00:00.000Z", "ackAt": null }
  ]
}
```

### Missing queue (`check`)

```json
{ "queueId": "uuid", "exists": false, "archived": true }
```

(`archived` is only present when a matching archived queue exists in the SQLite store.)

### `next` action envelope

```json
{
  "action": "next",
  "acked": { "index": 0, "text": "Run migrations", "ackAt": "2026-03-04T20:05:00.000Z" },
  "cleanup": { "policy": "archive", "action": "archived" }
}
```

When present, `cleanup` means a cleanup action was applied (most commonly on completion).

### `clear` output

```json
{ "queueId": "uuid", "action": "clear", "removed": true }
```

### `--all` output

```json
{
  "queues": [
    { "queueId": "uuid", "done": 1, "total": 2, "complete": false, "next": "Update changelog", "updatedAt": "2026-03-04T20:06:00.000Z" }
  ],
  "anyPending": true
}
```

## JSON errors

When `--json` is set and an error occurs (exit code `2`), stdout is:

```json
{ "error": { "code": "IO_ERROR", "message": "..." } }
```

Known `error.code` values:

- `INVALID_UUID`
- `LOCK_TIMEOUT`
- `CORRUPT_STATE`
- `IO_ERROR`
- `USAGE_ERROR`
- `INTERNAL_ERROR`

## Environment

`NUDGE_LOCK_TIMEOUT_MS` overrides the lock acquisition timeout (default: `5000`).

## Development

This repo uses pnpm (see `pnpm-lock.yaml`).
The repo requires Node `22.13.0+` and includes [`.nvmrc`](/Users/stevennevins/Documents/ai/git-remind/.nvmrc) for `nvm use`.

```bash
nvm use
pnpm install
pnpm test
pnpm run build
```

## Manual Verification

Build first:

```bash
pnpm run build
```

Verify the default archive flow:

```bash
CLI="$PWD/dist/src/index.js"
TMP_DIR="$(mktemp -d)"
mkdir -p "$TMP_DIR/.git"

ID="$(
  cd "$TMP_DIR" &&
  node "$CLI" queue "Run migrations" "Update changelog"
)"
test -f "$TMP_DIR/.git/nudge/nudge.sqlite"

(
  cd "$TMP_DIR" &&
  node "$CLI" "$ID" --json &&
  node "$CLI" next "$ID" --json &&
  node "$CLI" next "$ID" --json &&
  node "$CLI" "$ID" --json
)
```

Expected:

- the SQLite file exists at `.git/nudge/nudge.sqlite`
- the first `check` shows `complete: false`
- the second `next` returns `cleanup: { "policy": "archive", "action": "archived" }`
- the final `check` returns `{ "exists": false, "archived": true }`

Verify delete cleanup:

```bash
CLI="$PWD/dist/src/index.js"
TMP_DIR="$(mktemp -d)"
mkdir -p "$TMP_DIR/.git"

ID="$(
  cd "$TMP_DIR" &&
  node "$CLI" queue "One"
)"

(
  cd "$TMP_DIR" &&
  node "$CLI" next "$ID" --json --cleanup delete &&
  node "$CLI" "$ID" --json
)
```

Expected:

- `next` returns `cleanup: { "policy": "delete", "action": "deleted" }`
- the final `check` returns `{ "exists": false }` without `archived: true`

Verify fallback storage outside git:

```bash
CLI="$PWD/dist/src/index.js"
TMP_DIR="$(mktemp -d)"

ID="$(
  cd "$TMP_DIR" &&
  node "$CLI" queue "Outside git"
)"
test -f "$TMP_DIR/.nudge/nudge.sqlite"

(
  cd "$TMP_DIR" &&
  node "$CLI" "$ID" --json
)
```

## Git hook (optional)

If you want “block pushes when *any* pending queue exists in this repo”:

```bash
cp hooks/pre-push .git/hooks/pre-push
chmod +x .git/hooks/pre-push
```