# perk stage registry — the language-neutral contract both planes read (Q4/Q6).
#
# Authored once here, bundled into each build artifact (wheel -> perk/_shared/,
# npm tarball -> shared/), and parsed directly by both planes at runtime: the
# Python CLI generates its subcommands from it (T4); the TS extension drives its
# in-session transitions from it (Phase 1+). No codegen step (Q6).
#
# T2 locks the descriptor *shape*, the stage *graph*, and the state-key
# *vocabulary*. The drift-prone per-stage state I/O (`requires`/`reads`/`writes`)
# is left empty and filled as each handler lands (Phase 1+) — locking the shape,
# not authoring fiction about unbuilt stages.

schema_version: 1

# ---------------------------------------------------------------------------
# State-key vocabulary (Q4). `requires`/`reads`/`writes` draw ONLY from these,
# written as dotted "<tier>.<key>" strings (e.g. "github.plan"). The self-check
# flattens this map to a set for membership validation.
# ---------------------------------------------------------------------------
state_keys:
  # Tier 1 · GitHub — the canonical source of truth.
  github:
    - plan            # the plan issue (header + body)
    - objective       # the objective issue (storage + mechanics live in P2.T9; its stage in T10)
    - pr              # the pull request
    - labels          # repo labels (e.g. perk:plan)
    - comments        # discussion comments
    - review-threads  # review threads (distinct API from comments)
    - learn           # the perk:learn knowledge-capture issue (P2.T8b)
  # Tier 2 · .perk/workflow/ — the local cache (see contracts.md §8.1).
  cache:
    - plan            # materialized plan body cache
    - plan-ref        # provider-agnostic plan->branch ref (see contracts.md §8.4)
    - scratch         # per-run inter-process workflow files
    - handoff         # pre-session CLI->extension cold-door state
    - markers         # existence-based friction semaphores (e.g. pending-learn)
    - session-data    # the run-scoped session data dir artifacts (scratch/runs/<run_id>/data/; #339 Node 2.1)
  # Tier 3 · session entries — transient in-session state.
  session:
    - workflow-state  # the single `perk:workflow-state` record (contracts.md §8.3)

# ---------------------------------------------------------------------------
# Stages — the loop (Q5): objective-author -> objective-save -> objective-plan -> plan ->
# save -> implement -> submit -> address -> land -> learn.
#
# Every field is locked EXCEPT `requires`/`reads`/`writes` (empty until handlers
# land). `run_id` is uniform AND shape-locked by the validator: `perk/substrate/registry.py`
# `_check_doors_and_run_id` enforces `warm == keep` and `cold_local`/`cold_remote == mint`
# for every stage (Q2). It is a shape-locked map, NOT free per-stage data — the field is kept
# because the shape is the contract, not because stages vary.
# `command` is the human/contract LABEL for the stage's command, not the routing table: the
# actual dedicated cold-door routing lives in code via `perk/cli/stages.py` `DEDICATED_STAGES`
# (the generic launcher generator skips those ids). The field documents intent; it does not route.
# `doors.cold_remote` records which stages a remote runner may run (cli-vs-pi §4.5) —
# a settled design decision, not merely "not built yet". It is `true` exactly on the
# bounded agentic stages (`implement`, `address`), where the goal is already pinned by
# a plan or by reviewer feedback. The interactive/exploratory stages (planning above
# all) and the quick deterministic human-gate ops (`submit`/`land`/`learn`) are
# local-only by design — review, merge, and judgment capture stay where a human is.
# ---------------------------------------------------------------------------
stages:
  - id: objective-author
    summary: Draft a new objective + roadmap (read-only authoring).
    mode: read-only
    worktree: none            # interactive authoring, like plan — no branch, no objective yet
    doors: { warm: true, cold_local: true, cold_remote: false }  # interactive local authoring
    run_id: { warm: keep, cold_local: mint, cold_remote: mint }
    command: objective author  # P3.T2: dedicated seeded cold door (perk objective author)
    requires: []                       # nothing exists yet — this stage CREATES the objective
    reads: []
    writes: [session.workflow-state, cache.session-data]   # the read-only `mode` append (the gate toggle); #352 Node 2.1: objective_draft writes the objective-draft artifact + its session_artifacts pointer
    predecessors: []                   # the new single initial (authoring precedes everything)
    successors: [objective-save]

  - id: objective-save
    summary: Persist the drafted objective to GitHub; the read-only -> read-write boundary.
    mode: read-write
    worktree: none            # still no branch; save writes the objective issue
    doors: { warm: true, cold_local: true, cold_remote: false }
    run_id: { warm: keep, cold_local: mint, cold_remote: mint }
    command: objective save   # P3.T2: the objective_save terminating tool + /objective-save command
    requires: []
    reads: []
    writes: [github.objective, session.workflow-state]  # create the objective issue; link active_objective
    predecessors: [objective-author]
    successors: [objective-plan]

  - id: objective-plan
    summary: Select the next objective node and author a bounded plan (read-only).
    mode: read-only
    worktree: none            # interactive planning, like plan — no branch yet
    doors: { warm: true, cold_local: true, cold_remote: false }  # interactive local exploration
    run_id: { warm: keep, cold_local: mint, cold_remote: mint }
    command: objective plan   # P2.T10: dedicated cold door (perk objective plan) + /objective-plan
    requires: [github.objective]                        # an objective issue must exist
    reads: [github.objective]                           # roadmap nodes + next-node selection
    writes: [github.objective, session.workflow-state, cache.session-data]  # mark node planning; set active_objective; #339 Node 2.1: plan_draft writes the working-plan artifact + its session_artifacts pointer
    predecessors: [objective-save]                      # P3.T2: objective-save now precedes planning
    successors: [plan]

  - id: plan
    summary: Explore the codebase and draft a plan (read-only).
    mode: read-only
    worktree: none            # no implementation branch exists yet
    doors: { warm: true, cold_local: true, cold_remote: false }
    run_id: { warm: keep, cold_local: mint, cold_remote: mint }
    command: plan             # slash = /plan (perk-owned P2.T2a; toggles the read-only tool gate)
    requires: []
    reads: []
    writes: [session.workflow-state, cache.session-data]  # P2.T2a: /plan enter/exit appends `mode`; #339 Node 2.1: plan_draft writes the working-plan artifact + its session_artifacts pointer
    predecessors: [objective-plan]    # P2.T10: objective-plan is the new single initial
    successors: [save]

  - id: save
    summary: Persist the plan to GitHub; the read-only -> read-write boundary.
    mode: read-write
    worktree: none            # still no branch; save writes the plan issue + ref
    doors: { warm: true, cold_local: true, cold_remote: false }
    run_id: { warm: keep, cold_local: mint, cold_remote: mint }
    command: plan save        # cold: the merged `perk plan save` door (Node 3.2); warm twin: the plan_save tool + /plan-save command
    requires: []
    reads: []
    writes: [github.plan, cache.plan-ref, session.workflow-state]  # T2a: plan issue; T2b: plan-ref pointer; T3: warm door appends active_plan_ref
    predecessors: [plan]
    successors: [implement]

  - id: implement
    summary: Do the work on a branch (requires fresh context; cold-only).
    mode: read-write
    worktree: create          # first stage that needs a branch/worktree (folds in erk's `prepare`)
    doors: { warm: false, cold_local: true, cold_remote: true }  # bounded agentic stage — remotely runnable
    run_id: { warm: keep, cold_local: mint, cold_remote: mint }
    command: implement
    requires: [cache.plan-ref]   # T4: cold door derives plan-<pr_id> from the active ref
    reads: [cache.plan-ref]      # T4: positions the worktree from the ref (agent reads github.plan)
    writes: [session.workflow-state]  # T4: the worktree session links active_plan_ref on session_start
    predecessors: [save]
    successors: [submit]

  - id: submit
    summary: Push the branch and open a draft PR.
    mode: read-write
    worktree: reuse
    doors: { warm: true, cold_local: true, cold_remote: false }
    run_id: { warm: keep, cold_local: mint, cold_remote: mint }
    command: pr submit
    requires: [cache.plan-ref]              # T5a: reads the active ref to derive the branch
    reads: [cache.plan-ref, github.plan]    # T5a: ref -> branch; plan issue title for the PR
    writes: [github.pr, github.plan]        # T5a: open the draft PR; populate staged branch/pr header
    predecessors: [implement]
    successors: [address]

  - id: address
    summary: Classify PR review feedback (isolated child) and resolve the threads.
    mode: read-write
    worktree: reuse
    doors: { warm: true, cold_local: true, cold_remote: true }  # bounded agentic stage — remotely runnable
    run_id: { warm: keep, cold_local: mint, cold_remote: mint }
    command: pr address
    # P2.T7: a spawned read-only child (borrowed pi-subagents) fetches + classifies feedback;
    # the parent fixes only actionable items and batch-resolves the threads (`resolve_review_threads`).
    requires: [github.pr]                                          # the PR whose feedback we address
    reads: [cache.plan-ref, github.pr, github.review-threads, github.comments]
    writes: [github.review-threads, github.comments, github.pr, session.workflow-state]
    predecessors: [submit]
    successors: [land]

  - id: land
    summary: Merge the ready/approved PR and reconcile; sets the pending-learn marker.
    mode: read-write
    worktree: reuse
    doors: { warm: true, cold_local: true, cold_remote: false }
    run_id: { warm: keep, cold_local: mint, cold_remote: mint }
    command: pr land
    requires: [github.pr]                    # T5b: the PR to merge must exist
    reads: [cache.plan-ref, github.pr, github.objective]   # T5b ref->branch->PR; T11a node lookup
    writes: [github.pr, cache.markers, github.objective, github.plan]   # T5b merge+marker; T11a mechanical node-done; §8.36 learn_state header stamp
    predecessors: [address]
    successors: [learn]

  - id: learn
    summary: Capture learnings from the landed run; clears the pending-learn marker.
    mode: read-write
    worktree: reuse           # pending-learn blocks worktree deletion until this runs (Q5)
    doors: { warm: true, cold_local: true, cold_remote: false }
    run_id: { warm: keep, cold_local: mint, cold_remote: mint }
    command: learn
    # P2.T8b: deepened from a thin marker-clear into a real knowledge-capture pass — with a
    # summary, create a perk:learn issue (idempotent via the perk:learn-scoped find_learn_issue) +
    # back-link comment, then clear pending-learn; without one, the thin marker-clear (no issue).
    # P2.T17: now launches PRIMED (launch.py _learn_prompt) — the cold session opens investigating
    # the landed change; bare warm /learn injects perk-learn guidance (interactive). No new I/O
    # keys (no new gateway op) — the existing learn tool / learn capture worker still persist.
    requires: [cache.markers]                # pending-learn gates the worktree
    reads: [cache.markers, cache.plan-ref]   # was it set? + the plan ref -> issue number/run_id
    writes: [cache.markers, github.learn, github.comments, github.plan]  # clear marker; create learn issue + back-link; §8.36 learn_state header stamp
    predecessors: [land]
    successors: []
