Imports:
  - Types:
      - SpecEntry
      - GitEntry
    Usages:
      - configuration
    From: goga_tool_pybuggy/config
  - Types:
      - PluginConfigKeys
    From: goga_tool_pybuggy/plugin

Usages:
  conventions: .goga/usages/conventions.md
  click: .goga/usages/cooks/click.md
  ruamel-yaml: .goga/usages/cooks/ruamel-yaml.md
  goga: .goga/usages/cooks/goga.md

Annotations: |
  Use `conventions` for code writing rules and testing.
  Use `click` for the command wrapper, interactive prompts (plugin scalar keys + specs survey),
    and mapping domain errors to click.ClickException.
  Use `ruamel-yaml` for round-trip editing of the consumer .goga/config.yml (merge with skip-existing) AND for emitting
    .goga/tools/pybuggy/config.yml with active values plus commented records (skipped optional fields and headers/loader examples).
  Use `goga` for the in-process initialization of the goga-project via goga.init per-field Questionnaire methods + FileGenerator.

  Top-level bootstrap command pybuggy init — initializes the goga-project (created when absent, recreated only on
  confirmation), ships the consumer usages of the api cell into the project, AND interactively builds
  .goga/tools/pybuggy/config.yml (built when absent, rebuilt only on confirmation). The handler `run_init` is the
  testable entry point; the click wrapper `init_cmd` delegates to `run_init`. The goga-project initialization is
  delegated to `run_goga_init`; the interactive config build is delegated to `build_pybuggy_config` (testable seam for
  the TTY survey), which delegates file emission to the pure `write_pybuggy_config`. The recreate decisions for both
  configs live in `run_init` (click.confirm, default no); the builders themselves always (over)write. Usages are
  discovered from the installed goga_tool_pybuggy.api package. Use relative imports inside the cell.

---

"run_init() -> exit_code:int":
  location: init.py
  annotations: |
    Handler for the init command: ensure the goga-project is initialized, build the pybuggy tool config, then bootstrap
    the consumer usages of the api cell into the project where the command is invoked.

    `exit_code`: 0 on success; non-zero goga-init/config-build exit code propagated on failure/cancel.

    Algorithm:
    1. Resolve the output root as the current working directory.
    2. Goga-project config: when <cwd>/.goga/config.yml does NOT exist, call `run_goga_init`; when it DOES exist,
       ask via click.confirm (default no) whether to re-run goga init and overwrite it, and only re-run on yes.
       If non-zero, return it immediately.
    3. Pybuggy tool config: when <cwd>/.goga/tools/pybuggy/config.yml does NOT exist, call `build_pybuggy_config`;
       when it DOES exist, ask via click.confirm (default no) whether to rebuild it, and only rebuild on yes.
       If non-zero (cancel/error) return it; on 0 (or a declined rebuild) continue. The recreate decision lives
       here in the orchestrator — `build_pybuggy_config` itself always (over)writes without checking.
    4. Discover every .usages/*.md under the api cell (incl. asserts) from the installed goga_tool_pybuggy.api package.
    5. For each discovered file, write its content to <cwd>/.goga/usages/cooks/pybuggy/<stem>.md.
    6. Register usage keys via `register_usages` with <cwd>/.goga/config.yml (idempotent, skip-existing).
    7. Register annotation lines via `register_annotations` with <cwd>/.goga/config.yml (idempotent by backtick reference).
    8. Log INFO for added, WARNING for skipped.
    9. Return 0.

    Requirements:
    - Each config (goga .goga/config.yml and pybuggy .goga/tools/pybuggy/config.yml) is created when absent and
      only recreated on explicit click.confirm (default no) when present; declining a recreate skips that step so
      a plain repeat run just re-copies usages and skips registered keys.
    - The config-build step (`build_pybuggy_config`) always (over)writes the tool config when it runs — the recreate
      gate is `run_init`'s, not the builder's.
    - The usages/annotations bootstrap is idempotent.

    Constraints:
    - Do not register pybuggy usages/annotations when goga init failed/was cancelled — propagate goga's exit code.
    - Do not copy usages of internal development cells (config, spec, output, matchcrest, plugin, commands).

    Use `run_goga_init`, `build_pybuggy_config`, `register_usages`, `register_annotations`. Use `click` for error mapping. Use `conventions` for logging/testing.

"build_pybuggy_config() -> exit_code:int":
  location: init.py
  annotations: |
    Interactive testable-seam that builds <cwd>/.goga/tools/pybuggy/config.yml on every call — the contract test seam
    (public, in __all__). Isolates all TTY prompts; callers/tests stub this routine via monkeypatch (like run_goga_init).

    `exit_code`: 0 on success (file written/overwritten); non-zero on cancel/error.

    Algorithm:
    1. Resolve the config path as <cwd>/.goga/tools/pybuggy/config.yml.
    2. Survey the scalar plugin keys from `PluginConfigKeys` (every member except HEADERS and LOADER): each prompt carries a
       descriptive text stating what the field is for. BASE_URL is required (empty re-prompts — cannot be skipped); the
       remaining scalars are optional (empty -> None/skipped).
    3. Survey specs interactively via `click`: for each spec prompt name, type (swagger|openapi), location (required), and
       optional git block (url, location, ref); construct a `SpecEntry` (with `GitEntry` when git given) keyed by name;
       repeat until the user stops, but require AT LEAST ONE spec — the loop re-prompts for the first spec name until one
       is entered (mirrors BASE_URL: a config without specs is invalid per `configuration`). Structure per
       `configuration`.
    4. Delegate emission to `write_pybuggy_config` with the config path, the scalar answers (member value -> value|None),
       and the specs mapping. The destination is always (over)written — no existence check, no overwrite confirmation.
    5. Return 0. On click.Abort return non-zero; on other Exception log + echo + return non-zero (never raises).

    Requirements:
    - Interactive (TTY via click); tests stub this routine, never the real prompts.
    - Every interactive prompt states what the field is for (description mirrors the ApiPlugin option semantics).
    - BASE_URL mandatory and a Jinja2 template; write_pybuggy_config always emits it as a literal block scalar with a
      'required, Jinja2 template' comment.
    - At least one spec is required — the survey cannot complete with zero specs (a config without specs is invalid).
    - Returns a code and never raises on cancellation/failure.

    Constraints:
    - Always (over)write the tool config — no existence check and no overwrite confirmation; the file is regenerated on every call.
    - Do not survey HEADERS or LOADER — emitted as commented examples by write_pybuggy_config.
    - Do not duplicate plugin key names — iterate `PluginConfigKeys`.

    Use `click` for prompts. Use `PluginConfigKeys` for the scalar key set. Use `SpecEntry` and `GitEntry` for specs.
    Use `write_pybuggy_config` for emission. Use `conventions` for logging/type hints/testing.

"write_pybuggy_config(path: Path, scalar_values: dict[str, str | None], specs: dict[str, SpecEntry])":
  location: init.py
  annotations: |
    Pure round-trip emitter of .goga/tools/pybuggy/config.yml from collected answers — active values plus commented
    records for skipped optional scalars and the complex headers/loader sections, and the active specs mapping. No TTY.

    `path`: target file path (<cwd>/.goga/tools/pybuggy/config.yml).
    `scalar_values`: mapping of `PluginConfigKeys` scalar member value -> answered value (str) or None (skipped).
    `specs`: mapping of spec name -> `SpecEntry`.

    Algorithm:
    1. Configure ruamel YAML for round-trip (preserve quotes, key order, comments) and raise its best_width
       (yaml.width) so long plain scalars — e.g. the git url — are not line-folded onto a continuation line;
       build a fresh CommentedMap.
    2. Iterate scalar members of `PluginConfigKeys` (all except HEADERS, LOADER) in canonical order; for each: value
       present -> active key:value — numeric members (timeout/assert_delay -> float, retries/assert_timeout -> int) are
       coerced to their ApiPlugin option type so the scalar is a number, not a quoted string (BASE_URL -> literal block
       scalar | always, never a plain scalar); None -> commented "# key:" line with a one-line explanation.
    3. Emit HEADERS as a commented multi-line example (a "# headers:" block, sample entry, dict note).
    4. Emit LOADER as a commented multi-line example (a "# loader:" block, packages/modules structure, note).
    5. Emit specs mapping as active YAML: name: { type, location, git: { url, location, ref } when present } per
       `SpecEntry`/`GitEntry` shape (see `configuration`). When GitEntry.ref is None, ref is documented as a standalone
       # ref: line on its own indented line after location (the post-value comment slot items['location'][2] —
       the slot ruamel's own loader uses for trailing comments; the eol/after slots are dropped on the last key of a
       block mapping) instead of an empty active key.
    6. Ensure parent dir of `path` exists; dump the document.

    Requirements:
    - The emitted document is valid for the config schema (see `configuration`): a specs mapping where each entry carries
      required type/location; scalar plugin keys ignored by Config (extra=ignore), so active/commented presence does not
      break the config cell loader.
    - The specs mapping is always non-empty (`build_pybuggy_config` enforces ≥1 spec) — never emit an empty or absent
      specs section (a config without specs is invalid).
    - Commented records use a leading "#" and a one-line explanation.
    - BASE_URL is always a literal block scalar and carries a 'required, Jinja2 template' marker on its own line above
      the key.
    - Output deterministic for the same inputs.

    Constraints:
    - Do not survey/prompt — pure (TTY-free); interactivity lives in build_pybuggy_config.
    - Do not read/merge an existing file — build from scratch and overwrite `path` on every call (build_pybuggy_config neither checks for nor confirms an existing file).
    - Do not use pyyaml — ruamel.yaml round-trip only.

    Use `ruamel-yaml` for CommentedMap, commented-record emission, dump. Use `PluginConfigKeys` for canonical scalar key
    set/order. Use `SpecEntry`/`GitEntry` for specs shape. Use `configuration` from Imports for schema compatibility.
    Use `conventions` for type hints/relative imports.

"run_goga_init() -> exit_code:int":
  location: init.py
  annotations: |
    In-process initialization of the goga-project, Python-tailored — the contract test seam (public, in __all__).

    Unlike goga's universal InitLogic flow (which prompts for the language via ask_language), this routine drives the
    per-field Questionnaire methods individually, hardcodes language="python" (pybuggy is a Python project), selects the
    Docker image from the python-only set (ask_image("python")), assembles a GogaConfigAnswers, and delegates file
    generation to FileGenerator().generate(InitAnswers(...)). InitLogic is NOT used. The Dockerfile is mandatory:
    dockerfile_path is hardcoded to ".goga/Dockerfile" (goga's optional ask_dockerfile_path is NOT called), so
    FileGenerator always creates the Dockerfile and always emits the top-level dockerfile field into .goga/config.yml.
    After generation, `install_pybuggy` appends a RUN goga install pybuggy -v 0.1.x line to the Dockerfile, installing
    pybuggy via the goga installer pinned to the hardcoded 0.1.x version line.

    `exit_code`: 0 on success; 1 on user cancellation (click.Abort) or generation failure (the routine returns a code
    and never raises, so run_init — which calls it outside its own try/except — relies on this).

    Algorithm:
    1. Instantiate Questionnaire() and FileGenerator() from goga.init (NO InitLogic — manual per-field orchestration).
    2. language = "python"  (hardcoded; ask_language is NOT called — pybuggy is a Python project).
    3. Collect the remaining config fields by calling the per-field Questionnaire methods individually:
       - (usages_prefill, annotations_prefill) = questionnaire.ask_base_convention()
       - codemanifest_usages       = questionnaire.ask_codemanifest_usages(usages_prefill)
       - codemanifest_annotations  = questionnaire.ask_codemanifest_annotations(annotations_prefill)
       - agent           = questionnaire.ask_agent()
       - image           = questionnaire.ask_image(language)        # python-only set: 3.10–3.14
       - dockerfile_path = ".goga/Dockerfile"                       # hardcoded mandatory path; ask_dockerfile_path
                                                                     # is NOT called — the Dockerfile cannot be skipped
       - env             = questionnaire.ask_env(agent)
       - pipeline_agent  = questionnaire.ask_pipeline_agent(agent)
       - pipeline_env    = questionnaire.ask_pipeline_env(pipeline_agent)
    4. Assemble GogaConfigAnswers(language="python", agent=..., image=..., pipeline_agent=..., pipeline_env=...,
       env=..., dockerfile_path=..., codemanifest_usages=..., codemanifest_annotations=...).
    5. generator.generate(InitAnswers(goga_config=config)) — goga writes .goga/config.yml and the Dockerfile
       (`FROM {image}` with a trailing newline).
    6. Call install_pybuggy(Path(_DOCKERFILE_PATH)) to append RUN goga install pybuggy -v 0.1.x to the Dockerfile
       goga just wrote; return 0 on success.
    7. On click.Abort (user cancel) return 1; on other Exception (incl. an I/O failure raised by `install_pybuggy`)
       log + echo + return 1 (replicates InitLogic.run()'s handling, which is no longer available since InitLogic is bypassed).

    Requirements:
    - Interactive (TTY prompts via click) — callers and tests stub this routine via monkeypatch to avoid prompts.
    - Returns a code and never raises on cancellation/failure so the caller can propagate it cleanly.
    - The Dockerfile is mandatory: dockerfile_path is hardcoded to ".goga/Dockerfile" (never None), so FileGenerator
      always creates the Dockerfile and always emits the top-level dockerfile field into .goga/config.yml.
    - After generation the Dockerfile also carries RUN goga install pybuggy -v 0.1.x appended by `install_pybuggy`.

    Constraints:
    - Do NOT call ask_language — language is hardcoded to "python" (pybuggy is a Python project).
    - Do NOT call ask_dockerfile_path — the Dockerfile is mandatory; dockerfile_path is hardcoded to ".goga/Dockerfile"
      (ask_dockerfile_path can return None and skip creation).
    - Do NOT use InitLogic / ask / ask_goga_config — orchestrate the per-field methods manually to fix the language and
      restrict the image set.
    - Do NOT duplicate goga's prompt/generation logic — only orchestrate the existing ask_* methods (except
      ask_dockerfile_path, hardcoded) and FileGenerator.generate.
    - Do NOT modify goga FileGenerator — the pybuggy install line is appended post-generation by `install_pybuggy`.
    - Do NOT propagate an exception on cancellation/failure — return a code (run_init relies on this).

    Use `goga` from Usages for the per-field Questionnaire methods + FileGenerator.generate contract.
    Use `click` for click.Abort handling.
    Use `conventions` for type hints and structured logging on failure.

"install_pybuggy(dockerfile_path: Path) -> line:str | None":
  location: init.py
  annotations: |
    Append the pybuggy-install RUN line to the goga-generated Dockerfile, installing pybuggy via the goga installer
    pinned to the hardcoded 0.1.x version line so the consumer's test image carries pybuggy. Pure file augmentation
    of the Dockerfile written by goga FileGenerator (`FROM {image}`); no goga code is modified.

    `dockerfile_path`: path to the Dockerfile created by goga FileGenerator (cwd-relative, matching _DOCKERFILE_PATH).
    `line`: the appended RUN goga install pybuggy -v 0.1.x text; None when nothing was appended.

    Algorithm:
    1. No-op (return None) when `dockerfile_path` does not exist — goga may not have written it (e.g. FileGenerator
       mocked in tests); never create the file here.
    2. When _INSTALL_LINE (RUN goga install pybuggy -v 0.1.x) is already in the file content, return None (idempotent).
    3. Ensure the existing content ends with a newline, append _INSTALL_LINE, write the file back; log INFO; return the line.

    Requirements:
    - The install line is hardcoded RUN goga install pybuggy -v 0.1.x (goga installer, not pip); the version 0.1.x
      is pinned and never resolved dynamically.
    - The goga-written `FROM {image}` line is preserved; only the install line is appended.
    - Idempotent within a single Dockerfile: a repeated call (no regeneration in between) appends nothing.

    Constraints:
    - Do not create the Dockerfile when absent — only augment an existing one.
    - Do not resolve or vary the version — it is hardcoded 0.1.x.
    - An I/O error during read/write propagates to the caller (`run_goga_init` traps it and returns a code).

    Use `conventions` for code writing rules, structured logging, and testing.

"register_usages(config_path: Path, usage_keys: dict[str, str]) -> added_keys: list[str]":
  location: init.py
  annotations: |
    Round-trip edit of the consumer .goga/config.yml under codemanifest.usages: register each entry of `usage_keys`,
    skipping keys already present, and creating a minimal file when none exists.

    `config_path`: path to the consumer .goga/config.yml.
    `usage_keys`: mapping of pybuggy-<stem> to the relative path .goga/usages/cooks/pybuggy/<stem>.md to register.
    `added_keys`: the keys actually added (pre-existing keys are skipped and excluded).

    Algorithm:
    1. Configure ruamel YAML with preserve_quotes=True.
    2. When `config_path` exists, load it; when the load yields None (empty file), treat the document as empty.
       Navigate to codemanifest.usages, creating the nested CommentedMap nodes when missing.
    3. When `config_path` does not exist, build a fresh CommentedMap with a codemanifest.usages map.
    4. For each (key, value) in `usage_keys`: when the key is already in usages, skip it; otherwise insert it and
       append the key to `added_keys`.
    5. Ensure the parent directory of `config_path` exists and dump the document back to `config_path`.
    6. Return `added_keys`.

    Requirements:
    - Existing keys (including user-defined ones outside this command) are never overwritten — the run is idempotent.
    - Comments, key order, quotes, and block-scalars of the existing file are preserved.
    - When the file is absent, a minimal valid file carrying the codemanifest.usages block is created.

    Constraints:
    - Do not use pyyaml — it would reformat the file and drop comments.
    - Do not touch keys outside codemanifest.usages.

    Use `ruamel-yaml` for the round-trip load/modify/dump and the CommentedMap construction.
    Use `conventions` for type hints and relative imports.

"register_annotations(config_path: Path, annotation_lines: dict[str, str]) -> added_keys: list[str]":
  location: init.py
  annotations: |
    Round-trip edit of the consumer .goga/config.yml under codemanifest.annotations: append each annotation line of
    `annotation_lines`, skipping lines whose backtick reference (pybuggy-<stem>) already appears, and creating a
    literal block scalar (|) when none exists. The existing annotation text is preserved — only missing lines are
    appended.

    `config_path`: path to the consumer .goga/config.yml.
    `annotation_lines`: mapping of pybuggy-<stem> to one annotation line to append (each references the usage via a
      backtick).
    `added_keys`: the keys whose annotation line was actually added (pre-referenced keys are skipped and excluded).

    Algorithm:
    1. Configure ruamel YAML with preserve_quotes=True.
    2. When `config_path` exists, load it; when the load yields None (empty file), treat the document as empty.
       Navigate to codemanifest, resolving the annotations scalar (empty string when absent/null; ValueError when it
       exists but is not a scalar).
    3. When `config_path` does not exist, build a fresh CommentedMap.
    4. For each (key, line) in `annotation_lines`: when the line's backtick reference is already present in the
       annotation text, skip it; otherwise append the line (with a trailing newline) to the text and record the key
       in `added_keys`.
    5. When at least one line was added, write the text back as a literal block scalar (|) under
       codemanifest.annotations; ensure the parent directory of `config_path` exists and dump the document back.
    6. Return `added_keys`.

    Requirements:
    - Annotation lines whose backtick reference already appears are never duplicated — the run is idempotent.
    - Comments, key order, quotes, and block-scalars of the existing file are preserved.
    - The existing annotation text (base convention annotations from goga init) is preserved — only missing lines appended.
    - When the file is absent, a minimal valid file carrying the codemanifest.annotations block is created.

    Constraints:
    - Do not use pyyaml — it would reformat the file and drop comments.
    - Do not touch keys outside codemanifest.annotations.

    Use `ruamel-yaml` for the round-trip load/modify/dump and the LiteralScalarString block-scalar construction.
    Use `conventions` for type hints and relative imports.

"init_cmd(ctx: click.Context)":
  location: init.py
  annotations: |
    Click command wrapper for the top-level init command; carries no options or arguments, delegates to `run_init`, and
    propagates the returned exit code via ctx.exit.

    `ctx`: Click execution context used to control the process exit code.

    Use `click` for the command wrapper (@click.command("init"), @click.pass_context).

---

Author: Goga
CreatedAt: 27/07/26
Description: |
  Top-level init command handler — initializes the goga-project in-process via goga init (created when absent,
  recreated only on confirmation), then bootstraps the consumer usages of the api cell into the project where it is
  invoked: discovers .usages/*.md from the installed goga_tool_pybuggy.api package, copies them under
  .goga/usages/cooks/pybuggy/, and registers them in .goga/config.yml under codemanifest.usages; appends a referencing
  annotation line per usage under codemanifest.annotations; idempotent. Also interactively builds
  .goga/tools/pybuggy/config.yml (plugin options + specs) — built when absent, rebuilt only on confirmation. The
  generated .goga/Dockerfile carries a pybuggy install line via install_pybuggy (RUN goga install pybuggy -v 0.1.x)
  appended after goga generates it.
