Imports:
  - Types:
      - ProjectConfig
      - load_project_config
    Usages:
      - project-configuration
    From: goga/config
  - Types:
      - resync_registered_agents
    From: goga/connect

Usages:
  convention: .goga/usages/conventions.md
  click: .goga/usages/cooks/click.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

  Use the `click` practice to build the command, its options, the optional
  positional argument, the --no-connect flag, and exit-code propagation.

  Use the `project-configuration` practice when the bulk path needs to read
  .goga/config.yml — load_project_config is the single entrypoint for config consumption,
  and its tools field is a raw mapping validated structurally only.

  After a successful pip install in single and bulk mode, activate every agent
  recorded in ~/.goga/connect.yml via `resync_registered_agents`; the --no-connect
  flag suppresses this activation, and empty mode never activates.

---

"resolve_version(form: str | None) -> spec: str | None":
  location: install.py
  annotations: |
    Sole owner of the four-form version grammar. Maps a version-form string to a
    pip specifier; raises ValueError on operator-prefixed or malformed input.
    Used by both the single path (--version CLI flag) and the bulk path
    (config.tools expansion).

    `form`: version-form string in one of the four grammar forms, or None when
      the --version CLI flag is absent (single path only)
    `spec`: resolved pip specifier to append to the package identifier, or None
      when no specifier should be appended (latest / null marker)

    Algorithm:
    1. If `form` is None or equals the literal string "latest" → return None
       (no specifier — pip selects the newest version under the upgrade request)
    2. If `form` starts with a PEP 440 operator prefix (==, >=, <=, ~=, !=, <,
       >, ===) → raise ValueError (operator-prefixed forms are rejected — this
       routine owns the operator and emits it from the resolved grammar form)
    3. If `form` matches the major x-range pattern "N.x" (exactly one dot, the
       last segment is the literal "x", the first segment is a non-empty
       numeric) → return a compatible-release specifier pinning the lower bound
       on major version N (the form ~=N.0, PEP 440: >=N.0,<(N+1).0)
    4. If `form` matches the minor x-range pattern "N.M.x" (exactly two dots,
       the last segment is the literal "x", the first two segments are
       non-empty numerics) → return a compatible-release specifier pinning the
       lower bound on minor version N.M (the form ~=N.M.0, PEP 440:
       >=N.M.0,<N.(M+1).0). The trailing .0 in ~=N.M.0 is required — ~=N.M
       alone has only a major bound (<(N+1).0) and would NOT pin the minor
       upper limit
    5. If `form` matches the concrete-version pattern "N", "N.M", or "N.M.K"
       (dot-separated non-empty numeric segments, no trailing "x" literal) →
       return an exact-pin specifier (the form ==<form>)
    6. Otherwise → raise ValueError (malformed form)

    Distinguish major x-range ("1.x") from minor x-range ("1.0.x") by counting
    dots — do NOT use prefix matching. The count of dots determines the lower
    bound: one dot pins the major version (form ~=N.0, PEP 440 upper bound
    <(N+1).0), two dots pin the minor version (form ~=N.M.0, PEP 440 upper
    bound <N.(M+1).0). The trailing .0 in the minor case is what actually
    drives the tighter upper bound — do NOT emit ~=N.M (major-only bound).

    Apply `convention` for docstring style and the pure-function discipline
    (no side effects, deterministic output).

    Requirements:
    - The four accepted forms are: "N.x" (major x-range), "N.M.x" (minor
      x-range), "N(.M)?(.K)?" (concrete numeric), "latest"
    - None input is accepted and resolves to None — this exists for the absent
      --version CLI flag in the single path; it is NOT a valid config.tools
      value (loader rejects YAML-null structurally)
    - "latest" and None produce identical output (None) but "latest" is the
      only canonical no-specifier marker inside config.tools
    - Every operator-prefixed form (==, >=, <=, ~=, !=, <, >, ===) raises
      ValueError — resolve_version owns the operator and emits it from the
      resolved grammar form
    - Pip specifier output is always prefixed with the operator (== or ~=);
      no operator is injected when None is returned

    Constraints:
    - Do NOT accept pre-release, post-release, or local-segment versions
      (1.0.0a1, 1.0.0.post1, 1.0.0+local) — anything richer than the four-form
      grammar is rejected; pip handles richer forms after resolution
    - Do NOT validate that the numeric segments form a real PEP 440 version —
      the routine recognises shape (dot-separated numerics), not existence
    - Do NOT read .goga/config.yml — this routine is a pure transformer
    - Do NOT log or perform I/O — it is a pure function

"install(ctx: click.Context, name: str | None, sudo: bool, version: str | None, no_connect: bool = False) -> exit_code: int":
  location: install.py
  annotations: |
    Install one or more goga-tool packages into the current runtime interpreter
    via pip and, on success, activate every already-connected agent. Branches on
    `name`: the single path resolves --version via resolve_version and installs
    one package; the bulk path reads cfg.tools and installs all declared packages
    in a single pip invocation; the empty path prints "Nothing to install" and
    exits 0. After a successful pip in single and bulk mode, runs the activation
    re-sync unless `no_connect` is set. Propagates pip's returncode as the exit
    code whenever pip is invoked or re-sync is skipped; otherwise propagates the
    re-sync outcome (first non-zero per-agent failure).

    `exit_code`: pip's outcome when pip is invoked or re-sync is skipped; the
      first non-zero per-agent re-sync failure otherwise; 0 in the empty path.
    `ctx`: Click execution context used to control process exit codes.
    `name`: optional tool identifier without the goga-tool- / goga_tool_ prefix
      (CLI positional argument). When present, the single path runs and the
      config is ignored. When absent, the bulk/empty path runs from cfg.tools.
    `sudo`: when True, run pip under sudo with HOME preserved (Unix-only).
    `version`: optional version-form string in the four-form grammar. Used by
      the single path only (ignored in the bulk path). Resolved by
      resolve_version; operator-prefixed or malformed forms raise ValueError
      at resolution time.
    `no_connect`: when True, skip the post-install activation re-sync — the
      command performs the pip install only. Defaults to False (re-sync enabled).

    Algorithm:
    1. If `name` is not None → SINGLE PATH:
       a. Resolve `version` via resolve_version; on rejection, surface a
          user-facing CLI exception with a non-zero exit
       b. Compose the package identifier from `name` and the resolved specifier
          (empty when resolve_version returned None)
       c. Issue one pip install invocation against the current interpreter with
          the composed identifier and an upgrade request; apply sudo with HOME
          preservation when `sudo` is set
       d. Propagate pip's outcome as the pip exit code
    2. If `name` is None → BULK / EMPTY PATH:
       a. Load configuration via `load_project_config`; the result is a `ProjectConfig` instance.
          Loader exceptions (OSError, KeyError, ValueError, yaml.YAMLError)
          MUST be wrapped into a user-facing ClickException (non-zero exit) —
          never let a raw loader exception surface as a traceback.
       b. Read the tools mapping from cfg.tools (treat None as empty)
       c. If the mapping is empty → EMPTY PATH: print "Nothing to install" to
          stdout and exit 0 without invoking pip and without activation
       d. Else → BULK PATH: for each (tool_name, form) preserving insertion
          order, resolve the form via resolve_version (surface rejection as a
          user-facing CLI exception), compose the identifier, collect it; issue
          exactly one pip install invocation with every collected identifier
          and an upgrade request; apply sudo with HOME preservation when `sudo`
          is set; propagate pip's outcome as the pip exit code
    3. ACTIVATION (single and bulk paths only, after pip):
       a. If `no_connect` is True → keep the pip exit code and stop
       b. If the pip exit code is non-zero → keep the pip exit code and stop
       c. Otherwise call `resync_registered_agents` with the current user's goga
          home (~/.goga); the routine reads ~/.goga/connect.yml and re-activates
          every recorded agent
       d. The final exit code becomes the re-sync outcome (0 on full success or
          a missing/empty registry; otherwise the first non-zero per-agent failure)

    Apply `click` for the command shape, the optional positional argument, the
    flags, the option, ctx.pass_context, ctx.exit, click.echo for the empty-path
    message, and exit-code propagation. Apply `convention` for docstring style,
    import discipline, and structured logging. Apply `project-configuration` for
    load_project_config() semantics and the no-validation contract on cfg.tools.

    Requirements:
    - The single path MUST ignore cfg.tools entirely — name + flags fully
      determine the call
    - The bulk path MUST issue exactly one pip invocation whose argv contains
      every resolved goga-tool-<name><spec> in YAML order
    - The empty path MUST print "Nothing to install" to stdout and exit 0
      without invoking pip and without activation
    - The --version option is used by the single path only; the bulk path
      MUST NOT consult it
    - --sudo MUST apply sudo with HOME preservation to the (single) pip argv in
      both single and bulk modes; activation never runs under sudo
    - Activation MUST run only when pip succeeded (exit 0) in single or bulk
      mode and `no_connect` is False; it MUST NOT run in the empty path or after
      a non-zero pip
    - The final exit code MUST equal the pip outcome when pip failed, when
      `no_connect` is set, or in the empty path; otherwise it MUST equal the
      activation re-sync outcome
    - pip MUST be invoked through the current interpreter with an upgrade
      request present in every invocation

    Constraints:
    - Do NOT validate, parse, or modify `version` outside resolve_version —
      resolve_version is the sole owner of the grammar and the single point
      where malformed forms raise ValueError
    - Do NOT accept operator-prefixed forms in either --version or cfg.tools —
      they raise ValueError at resolution time
    - Do NOT install packages sequentially in the bulk path — all resolved
      packages MUST land in one pip argv
    - Do NOT auto-select sudo — the caller opts in via --sudo
    - Do NOT run activation under sudo — activation operates on the local user
      home; only pip honors --sudo
    - Do NOT write ~/.goga/connect.yml directly — activation goes through
      `resync_registered_agents`; connect is the single writer of the registry,
      and this command never writes it
    - Do NOT probe whether the package is already installed — the upgrade
      request handles it
    - Do NOT chunk the bulk argv — even a long argv is issued as a single pip
      invocation
    - On Windows, --sudo is unavailable (sudo is Unix-only)

---

Author: Goga
CreatedAt: 13/07/26

Description: |
  Installs one or more goga-tool packages into the current runtime interpreter
  via pip, propagates pip's outcome as the exit code, and — on success — activates
  every already-connected agent via the shared re-sync. Supports single-path install
  (one named tool with an optional four-form version), bulk-path install (every tool
  declared in config.tools in a single pip invocation), and empty-path no-op
  (Nothing to install). The four-form version grammar is owned by resolve_version;
  the --no-connect flag suppresses post-install activation.
