# Wave Executor — STATE.md Initialization (Pre-Wave 1b)

> Reference of the wave-executor skill, split out of `SKILL.md` (#1246). Body moved **byte-identical**; only this header is new.
> **Sibling-file paths inside this body are relative to the parent directory, not to `references/`**: `SKILL.md` → `../SKILL.md`, `wave-loop.md` → `../wave-loop.md`, `circuit-breaker.md` → `../circuit-breaker.md`. They were deliberately NOT rewritten, so the moved bytes stay verifiable against the pre-split file.
> Read at Pre-Wave 1b, before dispatching Wave 1 — see `../SKILL.md` § Pre-Wave 1b for the pointer.

## Pre-Wave 1b: Initialize STATE.md

Check [User-authorized housekeeping execution deviation](../../session-plan/SKILL.md#user-authorized-housekeeping-execution-deviation) before applying a default shape or mismatch prompt. When active, use the actual agreed plan's wave count and preserve its authorization record; the resolver's one-wave housekeeping result remains the audited default.

> Skip this section entirely if `persistence: false`.

Before dispatching Wave 1, write `<state-dir>/STATE.md` with YAML frontmatter and Markdown body:

```yaml
---
schema-version: 1
session-type: feature|deep|housekeeping
branch: <current branch>
issues: [<issue numbers from plan>]
session: <semantic session label, e.g. main-2026-01-01-deep-1>
session-id: <raw session_id from .orchestrator/session.lock — OMIT the key when no lock exists>
started_at: <resolveSessionStartedAt({ repoRoot }) — the lock's own started_at, NEVER new Date() at write time (#1368)>
status: active
current-wave: 0
total-waves: <from session plan>
---
```

```markdown
## Current Wave

Wave 0 — Initializing

## Wave History

(none yet)

## Deviations

(none yet)
```

**`started_at` and `session-id` are SOURCED, not typed (#1368).** Read both from the live session-lock before writing — never from the clock, and never from memory:

```bash
node -e "import('./scripts/lib/state-md.mjs').then(m => console.log(JSON.stringify({ started_at: m.resolveSessionStartedAt({ repoRoot: process.cwd() }), ...m.resolveSessionIds({ repoRoot: process.cwd() }) })))"
```

`resolveSessionStartedAt()` returns `.orchestrator/session.lock`'s own `started_at`, which the `orchestrator.session.lock.acquired` and `orchestrator.session.started` events agree with to the millisecond; it falls back to the current time only when no lock exists (`persistence: false`, or the acquire failed). Writing `new Date()` here instead put STATE.md 48 minutes ahead of the lock (measured 2026-09-13) and made `/close`'s #429 pre-check unable to join STATE.md to its own ledger record.

`session-id` is the raw/native harness id (`resolveSessionIds().session_id`), `session` the semantic label (`.semantic_session_id`). Both are OPTIONAL (`schema-version` stays `1`): when the lock yields `null`, OMIT the key entirely — never write a placeholder. Neither field grants lock or registry ownership; see `skills/_shared/state-ownership.md`.

Create the `<state-dir>` directory if needed (`mkdir -p <state-dir>`) before writing. This file is the persistent state record — other skills and resumed sessions read it.

**Then VALIDATE `total-waves` against the resolved shape — do not skip this.** A plan whose wave count the shape does not produce must never be dispatched silently:

```bash
node scripts/session-shape.mjs --repo-root "$PWD" \
  --session-type <session-type> [--profile <session-profile>] [--known-scope true|false] \
  --no-event | jq .totalWaves
```

`--no-event` is used HERE because the plan-time run already recorded `orchestrator.session.shape_resolved` — this is a re-read, not a second resolution. Compare the printed number with the plan's wave count (the value just written to `total-waves`):

- **User-authorized housekeeping deviation active** → before the first dispatch, persist the plan's execution-deviation record in STATE.md `## Deviations` via `appendDeviationOnDisk()` from `scripts/lib/state-md.mjs`, including the user request, default shape and actual plan/count. Validate `total-waves` against the actual plan, then continue with its normal scoped dispatch and review flow. Apply this branch even if both counts are one but the plan dispatches agents. Do not ask again to approve the same already-authorized shape, and do not rewrite the default-shape event to claim it describes the actual plan.
- **No authorized deviation and equal** → continue to Wave 1.
- **No authorized deviation and mismatch** → STOP. Surface it via `AskUserQuestion` per `.claude/rules/ask-via-tool.md`, with the shape's number and the plan's number both in the option descriptions: **re-plan to the shape (Recommended)** — rebuild the wave plan at the shape's wave count — versus **proceed with a logged Deviation**, which requires appending the divergence to STATE.md `## Deviations` (`appendDeviationOnDisk()` from `scripts/lib/state-md.mjs`) before the first dispatch.

#### Pre-Wave 1b Extension: Docs Tasks Persistence (A3 / #230)

After writing the base STATE.md frontmatter above, conditionally persist the docs tasks block emitted by session-plan:

**Condition:** BOTH of the following must be true:
1. The session plan contains a `### Docs Tasks (machine-readable)` section with a YAML code block.
2. `$CONFIG."docs-orchestrator".enabled` is `true`.

If either condition is false → omit the `docs-tasks` field entirely. Do NOT write an empty key (`docs-tasks: []`). Absence means "no docs tasks planned this session" — downstream consumers (session-end Phase 3.2) treat absence the same as an empty list.

When the condition is met, parse the YAML block from the session plan's `### Docs Tasks (machine-readable)` section and append the following field to the STATE.md YAML frontmatter (alongside the base fields above):

```yaml
docs-tasks:
  - id: <task id from plan>
    audience: <user|dev|vault>
    target-pattern: <glob pattern from plan>
    rationale: <rationale string from plan>
    wave: <wave number the task is assigned to>
    status: planned
```

Each entry's `status` is initialized to `planned`. session-end Phase 3.2 (Docs Verify) writes the terminal value per task: `ok` (diff is substantive), `partial` (diff region contains `<!-- REVIEW: source needed -->` markers), or `gap` (no matching diff). wave-executor does NOT perform intermediate status updates — `planned` remains until session-end runs.

> **Schema note:** `schema-version: 1` now includes the optional `docs-tasks` array. The field is backwards-compatible — its absence is a valid schema-version-1 STATE.md meaning "no docs tasks planned". Readers MUST treat a missing `docs-tasks` key identically to `docs-tasks: []`.

> **Ownership clarification:** session-plan does NOT write STATE.md directly. The wave-executor owns ALL STATE.md writes — initialization here (Pre-Wave 1b) is the canonical write point for `docs-tasks`. session-plan only emits the source `### Docs Tasks (machine-readable)` block for the coordinator to consume. See `skills/_shared/state-ownership.md` for the full ownership matrix.

> **Consumer cross-reference:** session-end reads `STATE.md` frontmatter's `docs-tasks` field (if present) during Phase 3.2 Docs Verify — see `skills/session-end/SKILL.md`. The field is also readable by the docs-writer agent if it needs to know which tasks were planned for the current session.

> **Ownership:** STATE.md is owned by the wave-executor. Only the wave-executor writes to it (initialization + post-wave updates). session-end reads it for metrics extraction and sets `status: completed`. session-start reads it only for continuity checks (Phase 0.5). No other skill should write to STATE.md.
