Merge feat/v0.17-sleep-setup-hybrid — local/remote/hybrid mode wizard

This commit is contained in:
Parfii-bot 2026-04-22 15:13:54 +08:00
commit 4c77b9a79b
5 changed files with 355 additions and 60 deletions

View file

@ -1,6 +1,6 @@
---
name: sleep-setup
description: One-time wizard (RULE 0.15) that configures KeiSeiKit v0.11 cloud REM sync. Generates an SSH deploy key, initializes the user's memory-repo, writes env refs (RULE 0.8), and emits a ready-to-paste `/schedule create` command for nightly consolidation. Pure-click except the 1 free-text field for repo URL.
description: One-time wizard (RULE 0.15) that configures KeiSeiKit sleep layer. Phase 0 picks mode (local-only / remote-only / hybrid); Phase 0b picks local trigger time (6 options incl. Custom HH:MM). Remote/hybrid generate an SSH deploy key, init the memory-repo, write env refs (RULE 0.8), and emit a `/schedule create` command. Local-only / hybrid emit a CronCreate snippet. Pure-click except 2 free-text fields (repo URL in Phase 2, Custom time in Phase 0b).
argument-hint: (no arguments)
---
@ -18,19 +18,24 @@ executed in order. Never skip a phase. Never re-order phases.
---
## Pipeline overview (6 phases, 9+ AskUserQuestion since v0.13.0)
## Pipeline overview (8 phases, 11+ AskUserQuestion since v0.14.0)
| Phase | File | Purpose | AskUserQuestion |
|---|---|---|---|
| 1 | [phase-1-repo-pick.md](phase-1-repo-pick.md) | Pick repo provider + visibility | 2 (click-only) |
| 2 | [phase-2-repo-url.md](phase-2-repo-url.md) | Collect SSH URL (1 free-text field) | 1 (AskUserQuestion `freeText`) |
| 3 | [phase-3-deploy-key.md](phase-3-deploy-key.md) | Run `kei-sleep-setup.sh`, show pubkey, confirm deploy-key added | 1 (click) |
| 0 | [phase-0-mode.md](phase-0-mode.md) | Pick sleep mode: local-only / remote-only / hybrid | 1 (click-only) |
| 0b | [phase-0b-time.md](phase-0b-time.md) | Pick local trigger time (6 options; Custom adds 1 free-text) | 1-2 (click; +freeText if Custom) |
| 1 | [phase-1-repo-pick.md](phase-1-repo-pick.md) | Pick repo provider + visibility (skipped if local-only) | 2 (click-only) |
| 2 | [phase-2-repo-url.md](phase-2-repo-url.md) | Collect SSH URL (skipped if local-only) | 1 (AskUserQuestion `freeText`) |
| 3 | [phase-3-deploy-key.md](phase-3-deploy-key.md) | Run `kei-sleep-setup.sh`, confirm deploy-key added (skipped if local-only) | 1 (click) |
| 3b | [phase-3b-deep-sleep.md](phase-3b-deep-sleep.md) | v0.13.0 — deep-sleep cadence + fork mode + store backend | 3 (click; +1 free-text if Custom cadence) |
| 4 | [phase-4-test-push.md](phase-4-test-push.md) | Dry-run a test commit via `kei-sleep-sync.sh` | 1 (click) |
| 5 | [phase-5-trigger.md](phase-5-trigger.md) | Render `/schedule create` command, offer to run now | 1 (click) |
| 4 | [phase-4-test-push.md](phase-4-test-push.md) | Dry-run a test commit (skipped if local-only) | 1 (click) |
| 5 | [phase-5-trigger.md](phase-5-trigger.md) | Render CronCreate and/or `/schedule create` per mode | 1-2 (click; hybrid asks twice) |
**Minimum AskUserQuestion count: 9.** All clicks except the single repo-URL
free-text in Phase 2 (plus optional Custom cadence / S3 fields in Phase 3b).
**Minimum AskUserQuestion count: 11** (remote-only / hybrid full pipeline).
**Local-only mode: 6 minimum** (Phases 0, 0b, 3b, 5; Phases 1-4 skipped).
All clicks except the repo-URL free-text in Phase 2 (skipped in local-only),
the Custom-time free-text in Phase 0b (optional), and the Custom-cadence /
S3 free-text fields in Phase 3b (optional).
---
@ -38,22 +43,28 @@ free-text in Phase 2 (plus optional Custom cadence / S3 fields in Phase 3b).
| Name | Set in | Meaning |
|---|---|---|
| `PROVIDER` | Phase 1 | `github` / `gitlab` / `bitbucket` / `self-hosted` |
| `VISIBILITY` | Phase 1 | `private` (recommended) / `public` (explicit user choice) |
| `REPO_URL` | Phase 2 | Validated SSH URL (`git@host:org/repo.git`) |
| `KEY_ADDED` | Phase 3 | boolean; was deploy key confirmed added? |
| `SLEEP_MODE` | Phase 0 | `local-only` / `remote-only` / `hybrid` |
| `SLEEP_TIME_LOCAL` | Phase 0b | `HH:MM` 24h format (e.g. `03:00`); user's local time |
| `PROVIDER` | Phase 1 (remote/hybrid only) | `github` / `gitlab` / `bitbucket` / `self-hosted` |
| `VISIBILITY` | Phase 1 (remote/hybrid only) | `private` (recommended) / `public` (explicit user choice) |
| `REPO_URL` | Phase 2 (remote/hybrid only) | Validated SSH URL (`git@host:org/repo.git`) |
| `KEY_ADDED` | Phase 3 (remote/hybrid only) | boolean; was deploy key confirmed added? |
| `DEEP_SLEEP_CRON_DAYS` | Phase 3b | integer ≥0; 0 disables Phase C; default 7 |
| `DEEP_SLEEP_WITH_FORK` | Phase 3b | 0 (plan only) / 1 (plan + fork branch) |
| `DEEP_SLEEP_WITH_FORK` | Phase 3b | 0 (plan only) / 1 (plan + fork branch) / 2 (plan + local-patch; local-only mode) |
| `STORE_BACKEND` | Phase 3b | `github` / `forgejo` / `gitea` / `filesystem` / `s3` |
| `TEST_VERIFIED` | Phase 4 | boolean; did the user see the test commit in the remote? |
| `SCHEDULE_ACTION` | Phase 5 | `run-now` / `copy-later` / `skip` |
| `TEST_VERIFIED` | Phase 4 (remote/hybrid only) | boolean; did the user see the test commit in the remote? |
| `SLEEP_CRON_UTC` | Phase 5 (remote/hybrid only) | `m h * * *` cron expression derived from `SLEEP_TIME_LOCAL` + local TZ |
| `SCHEDULE_ACTION` | Phase 5 | mode-dependent; see phase-5-trigger.md §5d |
---
## Final report (emit after Phase 5)
Remote-only / hybrid (full pipeline):
```
=== SLEEP-SETUP REPORT ===
Mode: <SLEEP_MODE>
Time (local): <SLEEP_TIME_LOCAL>
Provider: <PROVIDER> (visibility: <VISIBILITY>)
Repo URL: <REPO_URL>
Deploy key: ~/.ssh/keisei-memory-sync(.pub)
@ -63,19 +74,40 @@ Test push: <PASS/FAIL> (Phase 4)
Schedule: <SCHEDULE_ACTION>
```
If `SCHEDULE_ACTION == skip`, add:
Local-only (Phases 1-4 skipped):
```
Local-only mode. Traces will be pushed to the repo on every session end.
To enable nightly consolidation later: paste the prompt from Phase 5 into
`/schedule create` any time.
=== SLEEP-SETUP REPORT ===
Mode: local-only
Time (local): <SLEEP_TIME_LOCAL>
Provider: (skipped)
Repo URL: (skipped)
Deploy key: (not generated — local-only needs no git)
Sync repo path: (skipped)
Env refs: ~/.claude/secrets/.env (no sleep-sync keys written)
Test push: (skipped)
Schedule: <SCHEDULE_ACTION> # local-cron-created / -copy-later / -skipped
```
If `SCHEDULE_ACTION` contains `skipped` (remote) or `local-cron-skipped`,
add:
```
No nightly consolidation. Traces still land locally in
~/.claude/memory/traces/ (and push to the repo on every session end if
mode != local-only). Re-run /sleep-setup any time to register a trigger.
```
---
## Rules (apply throughout — enforced at every phase)
- **Pure-click contract.** Only Phase 2 asks for free text; every
decision is an `AskUserQuestion`. No `freeText` outside Phase 2.
- **Pure-click contract.** Only Phase 2 (repo URL) and Phase 0b (Custom
time) ask for free text; every other decision is an `AskUserQuestion`.
No `freeText` outside those two points (plus optional Custom cadence /
S3 fields in Phase 3b).
- **Local-only skips Phases 1-4 entirely** — no git operations, no SSH
key, no repo URL collection, no deploy-key walkthrough, no test push.
The skill jumps phase-0 → phase-0b → phase-3b → phase-5 and never
touches git.
- **Idempotent.** Re-running the wizard must NOT clobber existing
`~/.ssh/keisei-memory-sync` or `~/.claude/memory/sync-repo/`; the
helper script handles re-use.
@ -91,8 +123,10 @@ To enable nightly consolidation later: paste the prompt from Phase 5 into
repo leaks your session prompts and tool usage — confirm?".
- **Silent failure (RULE 0.15).** Nothing in the session-end path may
block the session from closing. The wizard itself may fail loudly.
- **Constructor Pattern (RULE ZERO).** Every phase file < 100 LOC
(well under the 200-LOC file limit).
- **Constructor Pattern (RULE ZERO).** Every phase file < 200 LOC
(RULE ZERO hard limit). New lightweight phases (0, 0b) target < 100
LOC; phase-3b and phase-5 are heavier branch-heavy router files and
sit between 100-200.
---

View file

@ -0,0 +1,64 @@
# Phase 0 — Sleep mode pick
Ask the user to pick the execution mode for nightly sleep-layer
consolidation. This is the FIRST phase of the wizard — it runs BEFORE
Phase 1 and branches the entire pipeline.
## 0a — Mode click
Emit ONE `AskUserQuestion`:
```json
{
"questions": [
{
"question": "How should sleep-layer consolidation run?",
"header": "Sleep mode",
"multiSelect": false,
"options": [
{"label": "Local-only", "description": "macOS CronCreate on this Mac; full access to ~/.claude/memory/ and /self-audit. No git repo needed."},
{"label": "Remote-only", "description": "Cloud agent via /schedule; git-repo based; morning git pull to read the report. Mac can sleep."},
{"label": "Hybrid", "description": "Both. Local cron does the deep analysis; remote is redundancy when Mac is asleep. Both paths are idempotent."}
]
}
]
}
```
Store the pick as `SLEEP_MODE` ∈ {`local-only`, `remote-only`, `hybrid`}.
## 0b — Branching
Branch the remainder of the pipeline based on `SLEEP_MODE`:
- **`local-only`** — skip Phases 1, 2, 3, 4 entirely. No git provider,
no SSH key, no repo URL, no deploy-key walkthrough, no test push.
Jump directly to Phase 0b (time) → Phase 3b (deep-sleep cadence,
adapted for local per phase-3b-deep-sleep.md) → Phase 5 (trigger,
emits only CronCreate).
- **`remote-only`** — proceed through Phase 0b (time) → Phase 1 →
Phase 2 → Phase 3 → Phase 3b → Phase 4 → Phase 5 (emits only
`/schedule create`).
- **`hybrid`** — same full pipeline as `remote-only`, but Phase 5
emits BOTH a CronCreate block AND a `/schedule create` block, with
two sequential AskUserQuestions (one per trigger path).
## 0c — Implication note
Print a one-line reminder before continuing:
```
SLEEP_MODE = <pick>. Local cron uses this Mac; remote trigger uses a
cloud Claude Code agent. Hybrid runs both; the two paths write to
different paths and are idempotent.
```
No second AskUserQuestion — the pick is final. Re-running the wizard
lets the user change modes later.
## Verify-criterion
- Exactly ONE `AskUserQuestion` in this phase.
- `SLEEP_MODE ∈ {local-only, remote-only, hybrid}`.
- If `local-only`, Phases 1-4 MUST be skipped by the caller.
- If `remote-only` or `hybrid`, the full pipeline continues at Phase 0b.

View file

@ -0,0 +1,79 @@
# Phase 0b — Sleep time picker
Ask the user to pick the local-time trigger for nightly consolidation.
Runs immediately after Phase 0 (mode pick), before any git setup.
## 0b.1 — Time click
Emit ONE `AskUserQuestion`:
```json
{
"questions": [
{
"question": "When should nightly consolidation run (local time)?",
"header": "Sleep time",
"multiSelect": false,
"options": [
{"label": "03:00", "description": "Classical REM peak — default"},
{"label": "00:00", "description": "Midnight — end of calendar day"},
{"label": "05:00", "description": "Pre-dawn — fresh morning report"},
{"label": "23:00", "description": "Late evening — before overnight sync"},
{"label": "21:00", "description": "Early evening — for early sleepers"},
{"label": "Custom", "description": "Pick exact HH:MM (24h format) on next prompt"}
]
}
]
}
```
## 0b.2 — Store or branch
- Non-custom pick → store as `SLEEP_TIME_LOCAL` verbatim (e.g. `03:00`).
- `Custom` → emit follow-up `AskUserQuestion` with `freeText`:
```json
{
"questions": [
{
"question": "Enter the local trigger time in HH:MM (24h). Example: 04:15 for 4:15 AM.",
"header": "Custom time",
"freeText": true
}
]
}
```
## 0b.3 — Validation
Validate with regex `^([01][0-9]|2[0-3]):[0-5][0-9]$`:
```bash
if ! echo "$SLEEP_TIME_LOCAL" | grep -qE '^([01][0-9]|2[0-3]):[0-5][0-9]$'; then
# invalid — re-ask with the same freeText prompt
# accept any leading zeros; reject "3:00" (must be "03:00"), "24:00", "12:60"
echo "Invalid time '$SLEEP_TIME_LOCAL'. Expected HH:MM with leading zeros (e.g. 03:00, 23:59)."
# loop back to 0b.2 Custom freeText prompt
fi
```
Retry loop: if invalid, re-emit the freeText prompt up to 3 times. After
3 failures, fall back to `03:00` and log `SLEEP_TIME_LOCAL defaulted to
03:00 after 3 invalid inputs`.
## 0b.4 — Confirmation line
Once validated, print:
```
SLEEP_TIME_LOCAL = <HH:MM> (this Mac's local time). Phase 5 will use
this value for the CronCreate expression and/or the UTC conversion for
the remote `/schedule` trigger.
```
## Verify-criterion
- At least ONE `AskUserQuestion` (two if Custom picked).
- `SLEEP_TIME_LOCAL` matches `^([01][0-9]|2[0-3]):[0-5][0-9]$`.
- No unclamped / unvalidated values stored. Invalid input either
re-prompts or falls back to `03:00` with an audit line.

View file

@ -4,6 +4,19 @@ Collect three pure-click decisions for Phase C (system consolidation):
cadence, fork mode, store backend. All three are `AskUserQuestion`
batches — zero free text (frequency "custom" is the single exception).
## Mode-dependent behaviour
If `SLEEP_MODE == local-only` (set in Phase 0), the fork mode question
in §3b.2 gets an EXTRA third option `plan+local-patch` that applies
auto-resolvable changes directly to `~/.claude/` files (after user
confirm at morning) instead of committing to a git branch. Cadence
(§3b.1) and store backend (§3b.3) are still asked, but the store
backend defaults to `filesystem` for local-only — the user can still
pick another if they want a secondary backup path.
For `remote-only` / `hybrid` the fork mode stays as the original 2
options (plan only / plan + fork branch).
## 3b.1 — Deep-sleep cadence
Emit ONE `AskUserQuestion`:
@ -39,7 +52,8 @@ Store as `DEEP_SLEEP_CRON_DAYS`:
## 3b.2 — Fork output mode
Emit ONE `AskUserQuestion`:
For `SLEEP_MODE ∈ {remote-only, hybrid}`, emit ONE `AskUserQuestion`
with 2 options:
```json
{
@ -57,7 +71,30 @@ Emit ONE `AskUserQuestion`:
}
```
Store as `DEEP_SLEEP_WITH_FORK` ∈ {0, 1}.
For `SLEEP_MODE == local-only`, emit ONE `AskUserQuestion` with 3
options (adds `plan+local-patch`):
```json
{
"questions": [
{
"question": "Fork output with applied changes?",
"header": "Deep-sleep fork (local mode)",
"multiSelect": false,
"options": [
{"label": "Plan only (Recommended)", "description": "Read markdown in the morning; decide by hand"},
{"label": "Plan + fork branch", "description": "Also generate deep-sleep/YYYY-MM-DD branch (needs a local git repo under ~/.claude/)"},
{"label": "Plan + local-patch", "description": "Auto-resolvable changes applied directly to ~/.claude/ after morning confirm — no git branch needed"}
]
}
]
}
```
Store as `DEEP_SLEEP_WITH_FORK` ∈ {0, 1, 2}:
- `0` — Plan only
- `1` — Plan + fork branch
- `2` — Plan + local-patch (local-only mode only)
## 3b.3 — Memory-repo backend
@ -110,7 +147,8 @@ free-text fields (one-off — unavoidable; S3 has no SSH-like default).
## 3b.5 — Verify-criterion
- `DEEP_SLEEP_CRON_DAYS ∈ {0,1,3,7,14, or 1..=90}` for custom.
- `DEEP_SLEEP_WITH_FORK ∈ {0, 1}`.
- `DEEP_SLEEP_WITH_FORK ∈ {0, 1}` for `remote-only` / `hybrid`;
`∈ {0, 1, 2}` for `local-only` (2 = plan+local-patch).
- `STORE_BACKEND ∈ {github, forgejo, gitea, filesystem, s3}`.
- `~/.claude/agents/_primitives/store-config.toml` exists and has
the active backend set.

View file

@ -1,47 +1,100 @@
# Phase 5 — Emit `/schedule create` command
# Phase 5 — Emit trigger (CronCreate and/or `/schedule create`)
Render the ready-to-paste nightly trigger and ask whether to run it now,
copy and do later, or skip to local-only mode.
Render the ready-to-paste nightly trigger(s) and ask how to register
them. Branches on `SLEEP_MODE` (set in Phase 0) and uses
`SLEEP_TIME_LOCAL` (set in Phase 0b).
## 5a — Load template
## 5a — Load template (remote path only)
Read `_primitives/templates/sleep-trigger-prompt.md` from the kit install
path:
If `SLEEP_MODE ∈ {remote-only, hybrid}`, read the cloud-agent template
from the kit install path:
```
~/.claude/agents/_primitives/templates/sleep-trigger-prompt.md
```
If the file is missing (older kit version), fall back to the inline
template in this phase file (see 5e).
template in this phase file (see 5f).
## 5b — Compute UTC cron
For `SLEEP_MODE == local-only` skip 5a entirely — no remote template is
rendered.
Local target: `03:00` user-local time, every day.
## 5b — Parse local time
`SLEEP_TIME_LOCAL` has format `HH:MM` (validated in Phase 0b). Convert
to minutes-past-midnight. The `10#` prefix prevents bash from
interpreting `08` / `09` as invalid octal:
```bash
hh=${SLEEP_TIME_LOCAL%:*}
mm=${SLEEP_TIME_LOCAL#*:}
local_minutes=$((10#$hh * 60 + 10#$mm))
```
## 5c — Compute UTC cron (remote path only)
Only needed if `SLEEP_MODE ∈ {remote-only, hybrid}`. CronCreate on the
Mac uses local time directly, so `local-only` skips this block.
```bash
# macOS / GNU date — detect local TZ offset in minutes
offset_min=$(date +%z | awk '{ s=substr($0,1,1); h=substr($0,2,2); m=substr($0,4,2); print (s=="-" ? 1 : -1) * (h*60+m) }')
# local 03:00 = 180 minutes past midnight local
local_minutes=180
utc_minutes=$(( (local_minutes + offset_min + 1440) % 1440 ))
utc_hour=$(( utc_minutes / 60 ))
utc_min=$(( utc_minutes % 60 ))
utc_cron=$(printf '%d %d * * *' "$utc_min" "$utc_hour")
SLEEP_CRON_UTC=$(printf '%d %d * * *' "$utc_min" "$utc_hour")
```
Keep arithmetic in the skill prompt if Claude Code executes shell;
otherwise have Claude compute the offset directly.
## 5d — Render blocks per mode
## 5c — Render placeholders
### Mode: `local-only`
Replace `{REPO_URL}` with `REPO_URL` and `{UTC_CRON}` with `utc_cron` in
the template. Print the rendered prompt to chat inside a fenced code
block so the user can one-click-copy.
Render ONE fenced `CronCreate` snippet (no `/schedule`). The cron
expression uses the user's local time directly — CronCreate runs on
this Mac, not in UTC:
## 5d — Click
```
CronCreate expression: <mm> <hh> * * * (local time on this Mac)
Prompt body:
Run /self-audit --cross-session on ~/.claude/memory/traces/.
Duration budget: 60 min max.
Always write summary to ~/.claude/memory/sleep-report-YYYY-MM-DD.md.
If >=3 recurring patterns detected, append a dated block to
~/.claude/memory/audit-backlog.md (section per RULE 0.14).
Invariants: append-only traces; no fabricated findings; skip
analysis if CWD was under a banned-project path.
```
Emit ONE `AskUserQuestion`:
Where `<mm>` and `<hh>` are the values from 5b. Emit ONE
`AskUserQuestion`:
```json
{
"questions": [
{
"question": "Register the local CronCreate now?",
"header": "Local cron",
"multiSelect": false,
"options": [
{"label": "Create CronCreate now", "description": "Invoke CronCreate with the rendered body"},
{"label": "Copy, create later", "description": "I'll register the cron myself with the snippet above"},
{"label": "Skip (no local cron)", "description": "No scheduled analysis — manual /self-audit only"}
]
}
]
}
```
Store `SCHEDULE_ACTION`:
- `Create CronCreate now``local-cron-created`
- `Copy, create later``local-cron-copy-later`
- `Skip``local-cron-skipped`
### Mode: `remote-only`
Render ONE fenced `/schedule create` block using the template from 5a.
Replace `{REPO_URL}` with `REPO_URL` and `{UTC_CRON}` with
`SLEEP_CRON_UTC`. Emit ONE `AskUserQuestion`:
```json
{
@ -52,7 +105,7 @@ Emit ONE `AskUserQuestion`:
"multiSelect": false,
"options": [
{"label": "Run /schedule now", "description": "Invoke /schedule create with the rendered prompt"},
{"label": "Copy, run later", "description": "Leave it to me — I'll paste into /schedule create myself"},
{"label": "Copy, run later", "description": "I'll paste into /schedule create myself"},
{"label": "Skip (local-only)", "description": "Just push traces; no nightly consolidation"}
]
}
@ -60,22 +113,40 @@ Emit ONE `AskUserQuestion`:
}
```
Handle:
- `Run now` → set `SCHEDULE_ACTION = run-now`; invoke
`/schedule create` with the rendered body.
- `Copy later` → set `SCHEDULE_ACTION = copy-later`; print the body
again and a one-line reminder.
- `Skip` → set `SCHEDULE_ACTION = skip`; print the local-only
footer from SKILL.md's "Final report".
Store `SCHEDULE_ACTION`:
- `Run /schedule now``remote-run-now`
- `Copy, run later``remote-copy-later`
- `Skip``remote-skipped`
## 5e — Fallback inline template (if kit missing the file)
### Mode: `hybrid`
Render BOTH blocks (CronCreate first, then `/schedule create`). Emit
TWO sequential `AskUserQuestion` batches — first the local question
from mode `local-only` (section 5d.local), then the remote question
from mode `remote-only` (section 5d.remote).
Store `SCHEDULE_ACTION` as a composite, e.g.
`local-cron-created+remote-run-now`,
`local-cron-copy-later+remote-skipped`,
`local-cron-skipped+remote-copy-later`, etc.
## 5e — Render placeholders (remote path only)
For `remote-only` / `hybrid`: replace `{REPO_URL}` and `{UTC_CRON}` in
the template. Print the rendered prompt inside a fenced code block so
the user can one-click-copy.
For `local-only`: no placeholders to render — the CronCreate body is
self-contained in 5d.
## 5f — Fallback inline template (remote path, if kit missing file)
If `~/.claude/agents/_primitives/templates/sleep-trigger-prompt.md` is
absent, use this minimal inline prompt:
```
Clone: <REPO_URL>
At UTC <utc_cron>:
At UTC <SLEEP_CRON_UTC>:
1. Clone shallow, read traces/ since reports/last-run.txt
2. Write reports/sleep-<date>.md with session + tool + error summary
3. If >=3 cross-session patterns, prepend to backlog.md
@ -89,7 +160,16 @@ missing from kit install; using fallback" so the user can re-install.
## Verify-criterion
- Exactly ONE `AskUserQuestion`.
- `local-only`: exactly ONE `AskUserQuestion`; exactly ONE fenced
CronCreate block; no `/schedule` rendered.
- `remote-only`: exactly ONE `AskUserQuestion`; exactly ONE fenced
`/schedule create` block; no CronCreate rendered.
- `hybrid`: exactly TWO `AskUserQuestion` batches (local first,
remote second); both blocks rendered.
- Cron expression on local path uses `SLEEP_TIME_LOCAL` directly (no
UTC conversion).
- Cron expression on remote path uses `SLEEP_CRON_UTC` from 5c.
- Rendered prompt contains no placeholder (`{REPO_URL}` / `{UTC_CRON}`).
- `SCHEDULE_ACTION` is one of `run-now` / `copy-later` / `skip`.
- The final report block from SKILL.md is emitted with real values.
- `SCHEDULE_ACTION` set per mode rules above.
- The final report block from SKILL.md is emitted with real values,
including `Mode: <SLEEP_MODE>` and `Time (local): <SLEEP_TIME_LOCAL>`.