Usages:
  convention: .goga/usages/conventions.md

Annotations: |
  The `convention` practice is used for:
  - Working with the codebase
  - Organizing the REPL development cycle
  - Debugging and testing
  - Organizing the test infrastructure
  - Understanding the general principles and rules of development and testing in the project

  This cell is a pure parser of project-level workflow-files. It reads a
  workflow-file, validates its structure (known top-level keys prompt /
  stages / extend, field types, loop counts, extend-entry positioning, and
  the inline agent/loop fields of an extend-entry), and returns a
  `WorkflowDocument` carrying declarative instructions for the compiler. It performs no I/O beyond the path it receives, performs
  no network or subprocess calls, and has no Imports — it depends only on
  the Python standard library and PyYAML.

  This cell is intentionally DECLARATIVE: it does NOT know about the compiler
  or any compiler-level concept (flow documents, flow stages, the compile
  routine, body embedding, depends_on derivation, command composition,
  loop-expansion, skills merging, stage REMOVAL or depends_on RECONNECTION).
  It returns instructions (per-stage overrides for stages — agent, prompt,
  loop, skills, AND a skip flag that instructs the compiler to DELETE the
  stage; NEW stages for extend — now carrying extracted inline agent/loop);
  the consumer
  (compiler) consumes them. This keeps the import graph one-directional: the
  compiler imports `WorkflowDocument` (and `WorkflowExtendStage`) from this
  cell, never the reverse. The cell extracts inline agent/loop from an
  extend-entry into the model (as it extracts before/after today) but does
  not resolve agent names to wrapper paths, does not embed extend-stages,
  does not derive depends_on, does not apply loop-expansion, does not merge
  skills, does not DELETE stages, and does not rewrite depends_on — all of
  that is the compiler's responsibility.

  Use `convention` for code style, dataclass usage, and test layout.

  approve is a declarative auto-approval instruction (one of the values
  "auto"/"plan"/"dialog", validated structurally). This cell extracts it into
  `WorkflowStage` / `WorkflowExtendStage`; the compiler
  consumes it to suppress a stage's interactive flag
  and/or emit auto_approve: true (each value drives a subset of these two
  effects). This cell performs NO approval logic — the import graph stays
  one-directional.

---

"WorkflowStage(agent: str | None = None, prompt: str | None = None, loop: int | None = None, skills: list[str] | None = None, skip: bool = False, approve: str | None = None)":
  location: workflow_stage.py
  annotations: |
    Data model of a single per-stage override instruction in a workflow-file —
    which agent, which prompt, how many loop iterations, which skills to merge,
    whether to SKIP (delete) the stage, and an optional auto-approval directive.
    Constructed by `parse_workflow` from one entry of the workflow-file stages
    map; carried verbatim inside `WorkflowDocument`.

    `agent`: agent name consumed by the compiler to compose the per-stage command
             wrapper path, or None when not specified
    `prompt`: per-stage prompt text consumed by the compiler as the stage
              description field, or None when not specified
    `loop`: positive integer (>= 1) instructing the compiler to expand the stage
            into N copies, or None when not specified
    `skills`: list of skill names the compiler merges with the stage's
              pipeline-file skills, or None when not specified
    `skip`: bool flag instructing the compiler to DELETE the stage. False by
            default (key absent / skip: false). True (skip: true) removes the
            stage and transparently reconnects dependents' depends_on
    `approve`: optional auto-approval directive consumed by the compiler.
               Accepted values are "auto", "plan", and "dialog" (validated by
               `parse_workflow`; any other value is a structural error). The
               compiler applies two INDEPENDENT effects, and each value drives a
               subset of them: "auto" drives BOTH (suppress the stage's
               interactive flag when the stage body has communication: true, AND
               emit auto_approve: true when the stage body's roles contain
               planner); "plan" drives ONLY the interactive-suppression effect;
               "dialog" drives ONLY the auto_approve effect. This cell does NOT
               act on `approve` — it is declarative; the compiler applies it.
               None when not specified.

    Build the data model with the standard library dataclasses module
    (NOT pydantic, per `convention`). Use @dataclass(kw_only=True).

    Requirements:
    - Use @dataclass(kw_only=True) (per `convention`)
    - All fields default to None; `skip` defaults to False (NOT None)
    - Field order is fixed: agent, prompt, loop, skills, skip, approve — matches
      the canonical order of the per-stage keys in the workflow-file
    - `approve` accepts ONLY "auto"/"plan"/"dialog"; any other value is rejected
      by `parse_workflow` as a structural error before this dataclass is built

    Constraints:
    - Do not validate loop >= 1 / skills list[str] / skip bool here — `parse_workflow`
      enforces these during parsing
    - Do not resolve agent to a wrapper path, merge skills, DELETE the stage, or
      RECONNECT dependents here — all the compiler's job
    - Do not act on `approve` here — it is declarative; the compiler performs the
      interactive-suppress / auto_approve logic when applying the workflow
  properties:
    "agent -> str | None": |
      Agent name consumed by the compiler to compose the wrapper path, or None.
    "prompt -> str | None": |
      Per-stage prompt text consumed by the compiler as the stage description.
    "loop -> int | None": |
      Positive iteration count (>= 1) for loop-expansion, or None.
    "skills -> list[str] | None": |
      Skill names merged with the stage's pipeline-file skills, or None.
    "skip -> bool": |
      Flag instructing the compiler to delete the stage and reconnect dependents.
    "approve -> str | None": |
      Optional auto-approval directive (one of "auto"/"plan"/"dialog").
      Declarative — extracted here, consumed by the compiler to suppress
      interactive / emit auto_approve (each value drives a subset of the two
      effects). None when not specified.

"WorkflowExtendStage(before: list[str] | None = None, after: list[str] | None = None, agent: str | None = None, loop: int | None = None, approve: str | None = None, body: dict[str, Any])":
  location: workflow_extend_stage.py
  annotations: |
    Data model of a single extend-entry — a new stage to be embedded into a
    target pipeline, carrying positioning instructions and the verbatim stage
    body. Constructed by `parse_workflow`; carried verbatim inside
    `WorkflowDocument`. The compiler embeds the stage and derives depends_on —
    this cell performs NO embedding and NO depends_on derivation.

    `before`: list of stage names the new stage precedes, or None
    `after`: list of stage names the new stage follows, or None
    `agent`: agent name composed into the new stage's wrapper path (default
             override; a stages-block entry for the same name wins), or None
    `loop`: positive integer (>= 1) for loop-expansion (default override), or None
    `approve`: optional auto-approval directive (one of "auto"/"plan"/"dialog",
               validated by `parse_workflow`), EXTRACTED from the extend-entry
               into the model — exactly like agent/loop. Acts as a DEFAULT
               override; the compiler consumes it against the extend-stage body
               (each value drives a subset of the two effects). None when not
               specified.
    `body`: verbatim copy of the stage body EXCLUDING before, after, agent, loop,
            approve, and depends_on. Open-ended — this cell does not know the
            stage field schema.

    Build the data model with the standard library dataclasses module
    (NOT pydantic, per `convention`). Use @dataclass(kw_only=True).

    Requirements:
    - Use @dataclass(kw_only=True) (per `convention`)
    - `before`/`after` default to None; `agent`/`loop`/`approve` default to None;
      `body` is required (no default)
    - Field order is fixed: before, after, agent, loop, approve, body
    - `approve` is EXTRACTED from the extend-entry (excluded from body) — like
      agent/loop; it never leaks into body as a stray stage field
    - `approve` accepts ONLY "auto"/"plan"/"dialog"

    Constraints:
    - Do not embed the stage or derive depends_on here — the compiler does both
    - Do not act on `approve` here — declarative; the compiler applies it
  properties:
    "before -> list[str] | None": |
      Stage names the new stage precedes, or None.
    "after -> list[str] | None": |
      Stage names the new stage follows, or None.
    "agent -> str | None": |
      Agent name composed into the new stage's wrapper path (default override).
    "loop -> int | None": |
      Positive iteration count for loop-expansion (default override), or None.
    "approve -> str | None": |
      Optional auto-approval directive (one of "auto"/"plan"/"dialog"), extracted
      inline from the extend-entry. Default override; the compiler consumes it
      (each value drives a subset of the two effects). None when absent.
    "body -> dict[str, Any]": |
      Verbatim stage body excluding before, after, agent, loop, approve, and
      depends_on. Open-ended.

"WorkflowDocument(prompt: str | None = None, stages: dict[str, WorkflowStage] | None = None, extend: dict[str, WorkflowExtendStage] | None = None)":
  location: workflow_document.py
  annotations: |
    Aggregated workflow-file document — the parsed representation of a
    workflow-file as a single value, combining an optional top-level prompt
    and a map of per-stage override instructions. Built by `parse_workflow`
    and consumed by the compiler via its workflow parameter.

    `prompt`: top-level prompt text that the compiler emits as the first
              top-level key of the compiled flow-file, or None when the
              workflow-file has no top-level prompt directive
    `stages`: map of per-stage override instructions keyed by stage name;
              an entry's key MUST match the name/id of a stage in the
              target pipeline-file. Stages in `stages` that do not match any
              pipeline stage OR any extend-stage are a STRUCTURAL ERROR at
              compile time — "unknown stage name in workflow.stages: <name>"
              (a workflow-file does not silently cover multiple pipelines;
              strict validation runs on the full original∪extend name set
              before skip removal, so a really-existing skipped stage is NOT
              flagged). An
              empty map (default) means the workflow provides only a
              top-level prompt and no per-stage overrides.
    `extend`: map of new-stage extend-instructions keyed by stage name; an
              entry is embedded into the target pipeline by the compiler and
              positioned via before/after. Stages in `extend` that reference
              unknown names are silently ignored with a warning by the compiler.
              An empty map (default) means the workflow provides no new stages.

    Build the data model with the standard library dataclasses module
    (NOT pydantic, per `convention`). Use @dataclass(kw_only=True).

    Requirements:
    - Use @dataclass(kw_only=True) (per `convention`)
    - `prompt` defaults to None
    - `stages` defaults to an empty dict via field(default_factory=dict)
      in the implementation; the signature default None is a DSL
      representation, the actual default factory is applied at construction
    - `extend` defaults to an empty dict via field(default_factory=dict)
      in the implementation; the signature default None is a DSL
      representation, the actual default factory is applied at construction
    - A workflow-file with none of a top-level `prompt`, any stage entries, or
      any extend entries is rejected by `parse_workflow` with a structural
      error before this dataclass is built — at least one must be present

    Constraints:
    - Do not validate stage-name keys against any pipeline — the compiler
      performs that match during apply and raises a structural error on
      names absent from both the pipeline and the extend-stages (strict
      validation; not a warning+skip). This cell does NOT validate
      names itself — it stays declarative
    - Do not mutate `stages` after construction — consumers treat the
      document as read-only
  properties:
    "prompt -> str | None": |
      Top-level prompt text emitted by the compiler as the first top-level key
      of the compiled flow-file, or None when the workflow-file has no
      top-level prompt directive.
    "stages -> dict[str, WorkflowStage]": |
      Map of per-stage override instructions keyed by stage name. Empty map
      when the workflow-file has no stages section.
    "extend -> dict[str, WorkflowExtendStage]": |
      Map of new-stage extend-instructions keyed by stage name. Empty map when
      the workflow-file has no extend section.

"parse_workflow(workflow_path: Path) -> workflow: WorkflowDocument":
  location: parse_workflow.py
  annotations: |
    Structurally parse a workflow-file into a `WorkflowDocument`. Read the
    file at `workflow_path`, parse it as YAML, validate the expected keys
    and field types, build `WorkflowStage` instances per entry, and return
    the aggregated `WorkflowDocument`. No content validation beyond the
    structural schema (key set, types, loop bounds); no agent-name
    resolution, no loop expansion, no depends_on rewriting — all of those are
    the compiler's responsibility.

    `workflow_path`: absolute path to the workflow-file
    `workflow`: the parsed `WorkflowDocument` carrying declarative
                instructions for the compiler

    Algorithm:
    1. Read `workflow_path` as text. On OSError (file missing, permission
       denied) — propagate the exception unchanged
    2. Parse the text as YAML. On invalid YAML — raise a structural
       error "invalid YAML in workflow-file"
    3. If the loaded value is not a dict (e.g. a scalar, a string, a list)
       — raise a structural error "workflow must be a mapping"
    4. Extract the optional top-level keys:
       - prompt: if present, must be a str; otherwise raise a structural
         error "non-str value in workflow.prompt"
       - stages: if present, must be a dict; otherwise raise a structural
         error "non-mapping stages block in workflow"
       - extend: if present, must be a dict; otherwise raise a structural
         error "non-mapping extend block in workflow"
    5. For every other top-level key — raise a structural error
       "unknown key in workflow: KEY; valid keys: prompt, stages, extend"
    6.1. For each entry of stages (when present), identified by stage name
       and stage value:
       6.1.1. If the stage value is not a dict — raise a structural error
          "non-mapping stage NAME in workflow.stages"
       6.1.2. Validate the key set of the stage value against agent, prompt,
          loop, skills, skip, approve: an unknown key raises "unknown key in
          workflow.stages.NAME: KEY; valid keys: agent, prompt, loop, skills, skip, approve"
       6.1.3. agent (when present) must be a str; otherwise raise
          "non-str value in workflow.stages.NAME.agent"
       6.1.4. prompt (when present) must be a str; otherwise raise
          "non-str value in workflow.stages.NAME.prompt"
       6.1.5. loop (when present) must be an int and >= 1; otherwise raise
          "non-int value in workflow.stages.NAME.loop" (non-int) or
          "loop must be >= 1 in workflow.stages.NAME" (int < 1)
       6.1.6. skills (when present) must be a list[str]; otherwise raise
          "non-list-of-str skills in workflow.stages.NAME"
       6.1.7. skip (when present) must be a bool; otherwise raise a structural
          error "non-bool value in workflow.stages.NAME.skip"
       6.1.8. approve (when present) must be a str and one of "auto"/"plan"/"dialog";
          otherwise raise "non-str value in workflow.stages.NAME.approve"
          (non-str) or "approve must be one of: auto, plan, dialog in
          workflow.stages.NAME" (str outside the set)
       6.1.9. Build a `WorkflowStage` from the validated values (agent, prompt,
          loop, skills, skip, approve)
    6.2. For each entry of extend (when present), identified by stage name
        and entry value:
        6.2.1. If the entry value is not a dict — raise a structural error
           "non-mapping extend entry NAME in workflow.extend"
        6.2.2. If the entry value contains a depends_on key — raise a structural
           error "depends_on is forbidden in workflow.extend.NAME"
        6.2.3. If the entry value contains a skip key — raise a structural
            error "skip is forbidden in workflow.extend.NAME"
        6.2.4. before (when present) must be a list[str]; otherwise raise
           "non-list-of-str before in workflow.extend.NAME"
        6.2.5. after (when present) must be a list[str]; otherwise raise
           "non-list-of-str after in workflow.extend.NAME"
        6.2.6. agent (when present) must be a str; otherwise raise
           "non-str value in workflow.extend.NAME.agent"
        6.2.7. loop (when present) must be an int and >= 1; otherwise raise
           "non-int value in workflow.extend.NAME.loop" (non-int) or
           "loop must be >= 1 in workflow.extend.NAME" (int < 1)
        6.2.8. approve (when present) must be a str and one of "auto"/"plan"/"dialog";
           otherwise raise "non-str value in workflow.extend.NAME.approve"
           (non-str) or "approve must be one of: auto, plan, dialog in
           workflow.extend.NAME" (str outside the set)
        6.2.9. If neither before nor after is present — raise a structural error
           "extend entry NAME requires at least one of before/after"
        6.2.10. Other keys of the entry value are NOT validated (open-ended:
           title, prompt, skills, roles, communication, and any other stage
           field) and pass through verbatim
        6.2.11. Build a `WorkflowExtendStage` from the validated before/after,
           agent/loop/approve, and the REMAINING entry value (excluding before,
           after, agent, loop, approve, and depends_on) as body — agent/loop/
           approve are extracted into the model, not carried in body, so they
           never reach the flow-file as stray stage fields
    7. If prompt is None AND stages is empty (no entries) AND extend is empty
       (no entries) — raise a structural error "empty workflow — provide at
       least prompt, one stage, or one extend entry"
    8. Return `WorkflowDocument` from the parsed prompt and stages

    Apply `convention` for code style, exception message formatting, and
    docstring style.

    Requirements:
    - Top-level unknown keys are a structural error — only prompt, stages,
      and extend are accepted
    - extend-entry depends_on is a structural error; before/after (when
      present) must be list[str]; at least one of before/after is required
    - extend-entry names are NOT validated against any pipeline — unknown
      before/after names pass through; the compiler decides whether to apply
      or ignore (silently with a warning)
    - Per-stage unknown keys are a structural error — only agent, prompt,
      loop, skills, skip, approve are accepted
    - skip (when present) must be a bool; a non-bool value is a structural
      error
    - skip is forbidden in an extend-entry — a structural error (skip is
      defined only for existing pipeline stages via the stages block)
    - skills (when present) must be a list[str]; a non-list-of-str value is
      a structural error
    - loop must be an int >= 1; zero, negative values, and non-int types
      are structural errors
    - approve (stages-block entry AND inline extend) must be a str and one of
      "auto"/"plan"/"dialog"; any other value or a non-str is a structural error
    - An extend-entry's inline agent (when present) must be a str, its
      inline loop (when present) must be an int >= 1, and its inline
      approve (when present) must be a str and one of "auto"/"plan"/"dialog" —
      same type/value rules as the stages block; non-conforming values are
      structural errors
    - Inline agent/loop/approve of an extend-entry are EXTRACTED into the
      model and excluded from the verbatim body (like before/after) — they
      do not pass through as stage fields
    - Stage-name keys are NOT validated against any pipeline IN THIS CELL
      — unknown stage names pass through; the compiler raises a structural
      error on names absent from both the pipeline and the extend-stages
      (strict validation; it does not silently apply or ignore with a
      warning). This cell stays declarative — it does NOT
      validate names itself
    - A single workflow-file can apply to several pipelines ONLY when
      every name it references exists in each target pipeline; a name
      absent from a target pipeline is a structural error (not a silent
      warning+skip) — split or prune such workflows
    - A workflow-file with neither prompt nor any stage entries is rejected
      — at least one must be present
    - agent value is NOT validated against a known agent set; absence of
      the wrapper file is surfaced by afm at invocation time
    - prompt contents (top-level and per-stage) are NOT validated — passed
      through verbatim to the consumer

    Constraints:
    - Do not resolve agent to a wrapper path here — the compiler performs
      that composition
    - Do not perform loop expansion here — the compiler expands based on the
      loop count
    - Do not rewrite depends_on here — the compiler handles external
      references after expansion
    - Do not validate stage names against any pipeline schema — the compiler
      performs the match (now raising a structural error on names absent from
      both the pipeline and the extend-stages)
    - Do not DELETE a stage or RECONNECT dependents when skip is True —
      skip is a declarative instruction; the compiler performs the removal
      and reconnection
    - Do not act on approve here — it is declarative; the compiler applies
      the interactive-suppress / auto_approve logic
    - Do not let an inline extend approve reach the extend-stage body —
      it is extracted into the model (like agent/loop)
    - Do not skip structural validation on missing files — OSError
      propagates unchanged (consistent with the compiler behavior)
    - Do not accept YAML files whose root is not a mapping — that is a
      structural error

---

Author: Goga
CreatedAt: 17/07/26
Description: |
  Pure parser of project-level workflow-files into declarative
  `WorkflowDocument` instructions that downstream consumers apply to
  extend a pipeline at compilation time.
