Scaffold Nightshift into this repo
The loop is committed in the target repo, not in this plugin: it runs
claude -p --setting-sources project, where installed plugins never load, so
the hooks only bite when they live under the repo's own .claude/. This skill
copies them in, proves the copy, and leaves the switch off. Announce: "Using
nightshift:init to scaffold the overnight landing loop."
Scripts live under this skill's plugin directory: resolve ${CLAUDE_PLUGIN_ROOT}
(or this file's ../../scripts/) and call them with node.
1. Look before writing
git remote get-url origin is a GitHub remote, gh auth status succeeds.
- Detect the stack the way
init.mjs --stack auto does (Cargo.toml, package.json,
pyproject.toml, go.mod, else generic) and say which verifier skeleton it gets.
- An existing
loop/, scripts/check, or .claude/hooks/ means this is not a
first init: run node <plugin>/scripts/init.mjs --check and stop with its
report instead.
- Is there a plan to land? If the user names one, pass
--plan <path>;
otherwise init scaffolds docs/plans/<today>-nightshift-smoke.md with one
harmless task so the first night has something to do.
2. Scaffold
node <plugin>/scripts/init.mjs --stack <stack> [--plan <path>] [--base <branch>]
Say what it wrote (it prints the list): loop/*, .claude/hooks/*,
scripts/check (never overwritten if present), docs/developing/landing.md
or docs/nightshift.md, the smoke plan, and the merged .claude/settings.json.
Then read loop/config back to the user: MERGE_MODE (protected when the base
branch has required checks, else wait) and EXPECTED_CHECKS (gate only when a
gate CI job exists; otherwise empty, and a human must fill it with the check
names GitHub reports, which preflight lists). --deny-rules also adds
permissions.deny entries; the hooks already deny in every permission mode.
3. Prove it, in this order
scripts/check from the repo root — last line must be CHECK OK. Edit the
skeleton until it is (it is deliberately narrower than CI: fast, quiet).
loop/land.sh --dry-run — reads the plan from the checkout, so the
uncommitted scaffold is enough. Expect STOP: frozen (the switch is unset)
or, with LANDING_STATE=run already set, STOP: would run task 1: ….
node --test .claude/hooks/*.test.mjs — the copied guard tests pass in situ.
- Commit the scaffold on a branch, one commit, and tell the user to open the
PR:
loop/, .claude/hooks/, .claude/settings.json, scripts/check, the
docs page, the plan. Stage paths explicitly. The user opens the PR;
this skill never pushes to the base branch.
node <plugin>/scripts/preflight.mjs — one line per check. Its plan check
is expected to FAIL at this point (the plan is on the scaffold branch, not
on origin/<base>) and says "merge the PR that carries it". Everything
else should be ok or warn; a FAIL on protection or checks is a
loop/config edit to make now.
4. Hand over
Print, verbatim from the landing doc, the first night by daylight:
gh variable set LANDING_STATE --body frozen # the switch exists, and is off
for l in land land:blocked land:retry; do gh label create "$l"; done
# merge the scaffold PR, then:
node <plugin>/scripts/preflight.mjs # all ok
gh variable set LANDING_STATE --body run
MAX=1 loop/land.sh # one task, watched
gh variable set LANDING_STATE --body frozen
and the launchd lines from loop/launchd.plist's header. Never install
launchd yourself; never flip the switch to run. Both are the user's.
Later: init.mjs --check reports each scaffolded file as unchanged, modified
locally, or template newer; --update overwrites only files still at their
stamped hash. That is how a repo picks up a plugin fix without losing its edits.
1---2name: init3description: Use when the user says "/nightshift:init", "set up nightshift here", "add the overnight landing loop to this repo", or asks how to run plans unattended in a repo that has no `loop/` directory. Scaffolds the loop, the two PreToolUse guards, a stack-specific verifier and the landing doc into the current repo, merges `.claude/settings.json`, proves it with a dry run, commits the scaffold on a branch, runs preflight and prints the first-night recipe. Do NOT use to run a night (the loop runs itself from launchd or the terminal), to triage one (use morning), or to write a plan (use plan). Do NOT re-run to update an existing scaffold; use `init.mjs --check` and `--update`. This is for the old landing loop; for Nightwatch use nightwatch-init.4---56# Scaffold Nightshift into this repo78The loop is committed in the target repo, not in this plugin: it runs9`claude -p --setting-sources project`, where installed plugins never load, so10the hooks only bite when they live under the repo's own `.claude/`. This skill11copies them in, proves the copy, and leaves the switch off. Announce: "Using12nightshift:init to scaffold the overnight landing loop."1314Scripts live under this skill's plugin directory: resolve `${CLAUDE_PLUGIN_ROOT}`15(or this file's `../../scripts/`) and call them with `node`.1617## 1. Look before writing1819- `git remote get-url origin` is a GitHub remote, `gh auth status` succeeds.20- Detect the stack the way `init.mjs --stack auto` does (Cargo.toml, package.json,21 pyproject.toml, go.mod, else generic) and say which verifier skeleton it gets.22- An existing `loop/`, `scripts/check`, or `.claude/hooks/` means this is not a23 first init: run `node <plugin>/scripts/init.mjs --check` and stop with its24 report instead.25- Is there a plan to land? If the user names one, pass `--plan <path>`;26 otherwise init scaffolds `docs/plans/<today>-nightshift-smoke.md` with one27 harmless task so the first night has something to do.2829## 2. Scaffold3031```32node <plugin>/scripts/init.mjs --stack <stack> [--plan <path>] [--base <branch>]33```3435Say what it wrote (it prints the list): `loop/*`, `.claude/hooks/*`,36`scripts/check` (never overwritten if present), `docs/developing/landing.md`37or `docs/nightshift.md`, the smoke plan, and the merged `.claude/settings.json`.38Then read `loop/config` back to the user: `MERGE_MODE` (`protected` when the base39branch has required checks, else `wait`) and `EXPECTED_CHECKS` (`gate` only when a40`gate` CI job exists; otherwise empty, and a human must fill it with the check41names GitHub reports, which `preflight` lists). `--deny-rules` also adds42`permissions.deny` entries; the hooks already deny in every permission mode.4344## 3. Prove it, in this order45461. `scripts/check` from the repo root — last line must be `CHECK OK`. Edit the47 skeleton until it is (it is deliberately narrower than CI: fast, quiet).482. `loop/land.sh --dry-run` — reads the plan from the checkout, so the49 uncommitted scaffold is enough. Expect `STOP: frozen` (the switch is unset)50 or, with `LANDING_STATE=run` already set, `STOP: would run task 1: …`.513. `node --test .claude/hooks/*.test.mjs` — the copied guard tests pass in situ.524. Commit the scaffold on a branch, one commit, and tell the user to open the53 PR: `loop/`, `.claude/hooks/`, `.claude/settings.json`, `scripts/check`, the54 docs page, the plan. Stage paths explicitly. The user opens the PR;55 this skill never pushes to the base branch.565. `node <plugin>/scripts/preflight.mjs` — one line per check. Its plan check57 is expected to FAIL at this point (the plan is on the scaffold branch, not58 on `origin/<base>`) and says "merge the PR that carries it". Everything59 else should be `ok` or `warn`; a `FAIL` on `protection` or `checks` is a60 `loop/config` edit to make now.6162## 4. Hand over6364Print, verbatim from the landing doc, the first night by daylight:6566```67gh variable set LANDING_STATE --body frozen # the switch exists, and is off68for l in land land:blocked land:retry; do gh label create "$l"; done69# merge the scaffold PR, then:70node <plugin>/scripts/preflight.mjs # all ok71gh variable set LANDING_STATE --body run72MAX=1 loop/land.sh # one task, watched73gh variable set LANDING_STATE --body frozen74```7576and the launchd lines from `loop/launchd.plist`'s header. Never install77launchd yourself; never flip the switch to `run`. Both are the user's.7879Later: `init.mjs --check` reports each scaffolded file as unchanged, modified80locally, or template newer; `--update` overwrites only files still at their81stamped hash. That is how a repo picks up a plugin fix without losing its edits.