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.

---

"WorkflowStage(agent: str | None = None, prompt: str | None = None, loop: int | None = None, skills: list[str] | None = None, skip: bool = False)":
  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,
    and whether to SKIP (delete) the stage. Constructed by
    `parse_workflow` from one entry of the workflow-file stages map;
    carried verbatim inside `WorkflowDocument`.

    `agent`: agent name (e.g. "codex", "claude") consumed by the compiler to
             compose the per-stage command wrapper path
             (/home/goga/bin/AGENT-as-claude.sh), 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 with ids NAME-1..N, or None when not
            specified (equivalent to loop=1 — no expansion)
    `skills`: list of skill names the compiler merges with the stage's
              pipeline-file skills (pipeline first, then these, deduplicated
              by value), or None when not specified (no merge — the
              pipeline-file skills pass through unchanged)
    `skip`: bool flag instructing the compiler to DELETE the corresponding
            stage from the compiled flow-file. False (default, key absent, or
            skip: false) means the stage is NOT skipped; True (skip: true)
            means the compiler removes the stage entirely and transparently
            reconnects its dependents' depends_on. Defaults to False per
            `convention` — None is reserved for fields with a meaningful
            absence, and for skip absent is equivalent to False

    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 four fields default to None — workflow-files may omit any field;
      the parser produces None for missing fields
    - `skip` defaults to False (NOT None) — a missing `skip` key and an
      explicit skip: false are equivalent (both = do not skip)
    - Field order is fixed: agent, prompt, loop, skills, skip — matches the
      canonical order of the per-stage keys in the workflow-file

    Constraints:
    - Do not validate loop >= 1 here — `parse_workflow` enforces it during
      parsing and raises a structural error before this dataclass is built
    - Do not validate that skills is a list[str] here — `parse_workflow`
      enforces it during parsing and raises a structural error before this
      dataclass is built
    - Do not validate that skip is a bool here — `parse_workflow` enforces it
      during parsing and raises a structural error before this dataclass is built
    - Do not resolve `agent` to a wrapper path here — the compiler performs
      that composition when applying the instruction
    - Do not merge `skills` with anything here — the compiler performs the
      merge with the pipeline-file skills when applying the instruction
    - Do not DELETE the stage or RECONNECT dependents' depends_on here —
      `skip` is a declarative instruction; the compiler performs the actual
      removal and depends_on reconnection when applying the workflow
    - Do not validate `prompt` contents — pass through verbatim to the consumer
  properties:
    "agent -> str | None": |
      Agent name consumed by the compiler to compose the per-stage command
      wrapper path, or None when not specified.
    "prompt -> str | None": |
      Per-stage prompt text consumed by the compiler as the stage description
      field, or None when not specified.
    "loop -> int | None": |
      Positive iteration count (>= 1) instructing the compiler to expand the
      stage into N copies, or None when not specified (no expansion).
    "skills -> list[str] | None": |
      Skill names the compiler merges with the stage's pipeline-file skills
      (pipeline first, then these, deduplicated by value), or None when not
      specified (no merge).
    "skip -> bool": |
      Flag instructing the compiler to delete the corresponding stage from the
      compiled flow-file. False (default / key absent / skip: false) = not
      skipped; True (skip: true) = compiler removes the stage entirely and
      transparently reconnects its dependents' depends_on.

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

    `before`: list of stage names the new stage precedes (the compiler adds
              this stage to the depends_on of each named stage), or None when
              not specified
    `after`: list of stage names the new stage follows (the compiler adds each
             named stage to this stage's depends_on), or None when not specified
    `agent`: agent name (e.g. "codex", "claude") the compiler composes into
             the new stage's command wrapper path, exactly as a stages-block
             `agent` override does — or None when not specified. Acts as a
             DEFAULT override; an explicit stages-block entry for the same
             name wins (per-field)
    `loop`: positive integer (>= 1) instructing the compiler to expand the new
            stage into N copies with ids NAME-1..N, exactly as a stages-block
            `loop` override does — or None when not specified (no expansion).
            Acts as a DEFAULT override; an explicit stages-block entry for
            the same name wins (per-field)
    `body`: verbatim copy of the stage body (title, prompt, skills, roles,
            communication, and any other stage field) EXCLUDING before, after,
            agent, loop, 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` and `after` default to None — an extend-entry may omit either;
      `parse_workflow` enforces that at least one is present before this
      dataclass is built
    - `agent` and `loop` default to None — an extend-entry may omit either
    - `body` is required (no default) — every extend-stage carries stage content
    - Field order is fixed: before, after, agent, loop, body

    Constraints:
    - Do not validate "at least one of before/after" here — `parse_workflow`
      enforces it during parsing
    - Do not validate that body excludes depends_on here — `parse_workflow`
      rejects depends_on in an extend-entry before this dataclass is built
    - Do not validate that body excludes agent/loop here — `parse_workflow`
      extracts agent/loop from the entry before this dataclass is built, so
      they never leak into body as stray stage fields
    - Do not validate loop >= 1 here — `parse_workflow` enforces it during
      parsing and raises a structural error before this dataclass is built
    - Do not embed the stage or derive depends_on here — the compiler performs both
    - Do not compose the agent into a wrapper path, expand the loop, or apply
      the per-field priority vs a stages-block entry here — all of that is
      the compiler's responsibility
    - Do not validate before/after names against any pipeline — the compiler
      matches and silently ignores unknown names with a warning
    - Do not accept skip in an extend-entry — `parse_workflow` raises a
      structural error "skip is forbidden in workflow.extend.<name>" before
      this dataclass is built. skip is defined only for existing pipeline
      stages (via the stages block); an extend-entry is a positional new stage
      (before/after + body) and carries no skip semantics. No skip field is
      added to this dataclass.
  properties:
    "before -> list[str] | None": |
      List of stage names the new stage precedes (compiler adds this stage to
      their depends_on), or None when not specified.
    "after -> list[str] | None": |
      List of stage names the new stage follows (compiler adds them to this
      stage's depends_on), or None when not specified.
    "agent -> str | None": |
      Agent name the compiler composes into the new stage's command wrapper
      path (default override; a stages-block entry for the same name wins),
      or None when not specified.
    "loop -> int | None": |
      Positive iteration count (>= 1) instructing the compiler to expand the
      new stage into N copies (default override; a stages-block entry for
      the same name wins), or None when not specified (no expansion).
    "body -> dict[str, Any]": |
      Verbatim stage body excluding before, after, agent, loop, 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. For each entry of stages (when present), identified by stage name
       and stage value:
       a. If the stage value is not a dict — raise a structural error
          "non-mapping stage NAME in workflow.stages"
       b. Validate the key set of the stage value against agent, prompt,
          loop, skills, skip: an unknown key raises "unknown key in
          workflow.stages.NAME: KEY; valid keys: agent, prompt, loop, skills, skip"
       c. agent (when present) must be a str; otherwise raise
          "non-str value in workflow.stages.NAME.agent"
       d. prompt (when present) must be a str; otherwise raise
          "non-str value in workflow.stages.NAME.prompt"
       e. 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)
       f. skills (when present) must be a list[str]; otherwise raise
          "non-list-of-str skills in workflow.stages.NAME"
       g. skip (when present) must be a bool; otherwise raise a structural
          error "non-bool value in workflow.stages.NAME.skip"
       h. Build a `WorkflowStage` from the validated values (agent, prompt,
          loop, skills, skip)
    6b. For each entry of extend (when present), identified by stage name
        and entry value:
        a. If the entry value is not a dict — raise a structural error
           "non-mapping extend entry NAME in workflow.extend"
        b. If the entry value contains a depends_on key — raise a structural
           error "depends_on is forbidden in workflow.extend.NAME"
        b2. If the entry value contains a skip key — raise a structural
            error "skip is forbidden in workflow.extend.NAME"
        c. before (when present) must be a list[str]; otherwise raise
           "non-list-of-str before in workflow.extend.NAME"
        d. after (when present) must be a list[str]; otherwise raise
           "non-list-of-str after in workflow.extend.NAME"
        e. agent (when present) must be a str; otherwise raise
           "non-str value in workflow.extend.NAME.agent"
        f. 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)
        g. If neither before nor after is present — raise a structural error
           "extend entry NAME requires at least one of before/after"
        h. 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
        i. Build a `WorkflowExtendStage` from the validated before/after,
           agent/loop, and the REMAINING entry value (excluding before,
           after, agent, loop, and depends_on) as body — agent/loop 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 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
    - An extend-entry's inline agent (when present) must be a str, and its
      inline loop (when present) must be an int >= 1 — same type rules as the
      stages block; non-conforming values are structural errors
    - Inline agent/loop 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 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.
