Usages:
  convention: .goga/usages/conventions.md
  requests: .goga/usages/cooks/requests.md
  beautiful_yaml: .goga/usages/cooks/beautiful_yaml.md
  connect_yml_schema: |
    Schema for ~/.goga/connect.yml — registry of connected agents.

    Format:
    ```yaml
    agents:
      <agent_name>:              # one of: claude, codex, cursor, opencode, qwen
        force_overwrite: <bool>  # per-agent; persisted by connect(), read by upgrade()/install() via resync_registered_agents
    ```

    Semantics:
    - Each call to connect(agents=[...], force_overwrite=X) updates entries for listed agents only
    - Entries for agents not in the current call are preserved
    - Missing file = "no agents connected yet" (read returns empty)
    - Written atomically via tmp file + rename

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

  The registry at ~/.goga/connect.yml is the single source of truth for connected
  agents and their per-agent force_overwrite; it has exactly one writer and is
  consumed read-only by activation re-sync.

  Use `requests` library for HTTP requests (DSL spec download).
  Use `beautiful_yaml` practice for writing ~/.goga/connect.yml.
  Use `connect_yml_schema` practice for the registry's structure and semantics.
  Use shutil for centralized file copying into ~/.goga/.
  Use os.symlink / pathlib.Path.symlink_to for creating agent-side symlinks into ~/.goga/.
  Read and parse ~/.goga/connect.yml as YAML during registry re-sync.
  Route all output through sys.stderr (click is not used).

---

"connect(agents: list[str], force_overwrite: bool = False) -> exit_code:int":
  location: connect.py
  annotations: |
    Install goga skills, commands, and pipelines centrally into ~/.goga/, create symlinks
    from each agent directory into ~/.goga/, install pipeline files into ~/.goga/pipelines/,
    and persist a per-agent connection record in ~/.goga/connect.yml.

    `agents`: list of target AI agents (required, non-empty). Supported: claude, codex, cursor, opencode, qwen
    `force_overwrite`: allow overwriting existing skills. Defaults to False. Persisted per-agent in connect.yml.
    `exit_code`: return code (0 success, 1 error)

    Algorithm:
    1. Validate that `agents` is non-empty; otherwise exit with code 1
    2. Resolve ~/.goga/ as the central installation root; create it if missing
    3. Install central assets into ~/.goga/{skills,commands,pipelines}:
       3.1. Purge existing ~/.goga/skills/goga-*, ~/.goga/commands/goga (if present)
       3.2. Copy goga/assets/commands/* → ~/.goga/commands/ (only used by agents that consume commands — claude, opencode, qwen)
       3.3. Copy goga/assets/skills/* → ~/.goga/skills/
       3.4. Fetch the DSL specification:
          - URL: https://raw.githubusercontent.com/qarium/codemanifest/refs/heads/0.0.x/specs/en.md
          - HTTP GET via `requests`
          - Write response to ~/.goga/skills/goga-cell/dsl.md
          - On network failure, exit with code 1
       3.5. Install skills from goga_tool_* packages:
          - Discover via importlib.metadata.packages_distributions()
          - For each goga_tool_<tool_name>: validate <package>/skills/<tool_name>/SKILL.md exists;
            copy <package>/skills/* into ~/.goga/skills/ with goga-tool- prefix
          - Conflict resolution: skip-with-warning when force_overwrite=False, overwrite when True
    4. For each agent in `agents`:
       4.1. Emit a diagnostic line "Connecting agent: <name>" to sys.stderr so the
          user can tell which agent is being processed (used by both direct
          connect and the re-sync loop in `resync_registered_agents`)
       4.2. Resolve the agent's target directory:
          - claude → ~/.claude/
          - codex → ~/.codex/
          - cursor → ~/.cursor/
          - opencode → ~/.config/opencode/
          - qwen → ~/.qwen/
       4.3. Pattern-matching purge of stale installations at symlink targets:
          - Scan ~/.<agent>/skills/ for entries matching goga-* naming pattern
            (both real directories AND stale symlinks); delete all matches
          - For claude, opencode, and qwen: also scan and delete ~/.<agent>/commands/goga if it exists
            as a real directory or stale symlink
       4.4. Create symlinks (after purge in 4.3, targets are clean):
          - For each ~/.goga/skills/goga-* entry: create symlink
            ~/.<agent>/skills/<basename> → ~/.goga/skills/<basename>
          - For claude, opencode, and qwen: also create ~/.<agent>/commands/goga → ~/.goga/commands
       4.5. On symlink creation failure (existing real dir not purged, Windows privilege error):
          - Emit clear message to stderr, do not corrupt existing state, continue with other agents
    5. Install pipelines by calling `install_pipelines` with target ~/.goga/pipelines/ and force_overwrite propagated
       (executed once after all agents; pipelines are shared)
    6. Update ~/.goga/connect.yml registry via the `beautiful_yaml` and `connect_yml_schema` practices:
       6.1. Read existing connect.yml if present; otherwise start with empty dict
       6.2. For each agent in `agents`: set/replace entry {<agent>: {force_overwrite: <force_overwrite value>}}
       6.3. Preserve entries for agents not in the current call
       6.4. Write atomically (tmp file + rename)
    7. Return 0 if all agents processed and install_pipelines succeeded; otherwise 1
       (the exit_code of `install_pipelines` MUST be propagated: if install_pipelines returns 1, connect returns 1)

    Requirements:
    - Fully recreate central goga directories at step 3.1 (delete + copy)
    - Create symlinks at step 4.4 (not copies); purging at 4.3 is mandatory
    - Pattern-matching purge principle (preserved from copy-based model): before creating symlinks,
      scan each ~/.<agent>/skills/ for entries matching the goga-* naming pattern (both real directories
      AND stale symlinks) and delete all matches; for claude, opencode, and qwen, also match ~/.<agent>/commands/goga.
      This guarantees idempotent re-creation of symlinks without leftover state.
    - Detect existing real directories at symlink targets and refuse to overwrite without purge
    - Do not modify content belonging to other extensions
    - Do not touch CLAUDE.md, settings.json, README.md in the target agent directories
    - Download dsl.md from the external repository on every central install
    - Use importlib.metadata from the standard library
    - Abort with code 1 if dsl.md download fails
    - Derive tool skill names as goga-tool-<skill_dir_name>
    - Skip any tool package that lacks an entry-point skill entirely
    - Recreate ~/.goga/pipelines/ on every run via install_pipelines (delete + copy)
    - Propagate `force_overwrite` to `install_pipelines` at step 5
    - Propagate `install_pipelines` exit_code into the final return value
    - Persist per-agent force_overwrite in connect.yml via the `connect_yml_schema` practice
      (format: agents dict, each agent maps to {force_overwrite: bool})

    Constraints:
    - Do not install pipelines into project-level .goga/pipelines/ (that directory is user-owned)
    - Do not write ~/.goga/connect.yml from outside this cell — `connect` is the single writer; the
      install and upgrade commands consume it read-only through `resync_registered_agents`
    - Windows symlink privilege limitations are documented as a known constraint;
      OSError from symlink_to MUST be caught and reported (do not crash)

"install_pipelines(pipelines_dir: Path, force_overwrite: bool = False) -> exit_code: int":
  location: install_pipelines.py
  annotations: |
    Recreate the user-level pipelines directory and populate it with flat *.yml files
    from the goga repository and from installed goga_tool_* packages.

    Tool-pipeline namespacing: every pipeline from a goga_tool_* package is installed under
    its tool prefix so it is addressable as goga pipeline <tool>:<name> — the file for
    goga_tool_<tool>/pipelines/<name>.yml is written as <tool>:<name>.yml. The tool prefix
    is normalized to the canonical hyphenated tool name: the underscored Python top-level
    module name (e.g. goga_tool_hello_world) becomes the user-facing tool identifier
    hello-world, so the pipeline is run as goga pipeline hello-world:<name>. Internal-source
    pipelines (goga/assets/pipelines/) are installed UN-prefixed. A tool pipeline occupies
    a <tool>:-prefixed stem, distinct from the un-prefixed internal-source stems and from
    other tools' <tool>: stems, so the internal and tool sources coexist as separate files.

    Residual conflict resolution: after namespacing a tool pipeline can only collide with an
    existing file when its namespaced destination <tool>:<name>.yml already exists. Such a
    residual conflict is resolved with the same `force_overwrite` semantics used for skill
    installation in the existing tool-skill installer (private helper _install_tool_skills in connect.py):

    - force_overwrite=False (default): the tool's pipeline is SKIPPED (a warning is logged to stderr,
      the existing file is preserved).
    - force_overwrite=True: the tool's pipeline OVERWRITES the existing file.

    `pipelines_dir`: target directory (typically ~/.goga/pipelines/)
    `force_overwrite`: when True, let a tool pipeline overwrite an existing file on a residual namespaced conflict
    `exit_code`: 0 on success, 1 on error

    Algorithm:
    1. Delete `pipelines_dir` if it exists, then (re)create it
    2. Resolve the internal source directory (goga/assets/pipelines/) shipped with the package
    3. Copy flat *.yml files from the internal source to `pipelines_dir`
       (internal source is always installed first; it establishes the base)
    4. Discover installed Python packages with the goga_tool_ prefix
       via importlib.metadata.packages_distributions()
    5. For each discovered package goga_tool_<tool_name>:
       - Resolve the package path and derive tool_name by dropping the goga_tool_ prefix
         and normalizing underscores to hyphens (goga_tool_hello_world -> hello-world),
         so the namespace prefix matches the canonical hyphenated tool/package name
       - If <package>/pipelines/ exists, for each flat *.yml file <name>.yml in it:
         - Namespace the destination as <pipelines_dir>/<tool_name>:<name>.yml
         - If that destination already exists (residual namespaced conflict):
           - If force_overwrite=False: log warning to stderr and skip this file
           - If force_overwrite=True: overwrite the destination with the tool's file
         - Otherwise: copy the tool's file to <pipelines_dir>/<tool_name>:<name>.yml
    6. Return 0 on success, 1 on error (e.g., OSError/shutil.Error during copy or rmtree)

    Requirements:
    - Fully recreate `pipelines_dir` at step 1 (delete + create)
    - Scan only the top level of each source — do not descend into subdirectories
    - Apply the `convention` practice for filesystem and package-discovery code
    - Skip missing source directories silently (a package without pipelines/ is not an error)
    - Namespace every goga_tool_* pipeline under its tool as <tool>:<name>.yml
      (internal-source pipelines stay un-prefixed)
    - Normalize the tool namespace prefix to the canonical hyphenated tool name by
      replacing underscores with hyphens in the name derived from the top-level module
      name (goga_tool_hello_world -> hello-world); the on-disk package layout is located
      by the underscored module name, but only the user-facing namespace prefix is normalized
    - Residual namespaced conflict resolution MUST mirror the existing tool-skill installer semantics
      (private helper _install_tool_skills in connect.py):
      skip-with-warning when force_overwrite=False, overwrite when force_overwrite=True
    - Route all output (warnings) through sys.stderr (per cell global annotation)

    Constraints:
    - Do not touch project-level .goga/pipelines/ — only the user-level ~/.goga/pipelines/
    - Do not parse or validate pipeline-file contents

"resync_registered_agents(goga_home: Path) -> exit_code: int":
  location: connect.py
  annotations: |
    Re-apply `connect` to every agent recorded in the registry, each with its own
    persisted force_overwrite, so agent-side skills, symlinks, and pipelines track
    the currently installed packages.

    `goga_home`: goga home directory that holds connect.yml (typically ~/.goga/)
    `exit_code`: 0 when every recorded agent re-synced, or when the registry is
      missing/empty; otherwise the first non-zero `connect` exit code

    Algorithm:
    1. Resolve the registry path <goga_home>/connect.yml
    2. If the registry file is absent, return 0 (no agents connected yet)
    3. Parse the registry as YAML per `connect_yml_schema`; on a parse failure,
       emit diagnostics to stderr and return non-zero
    4. If the parsed agents map is missing or empty, return 0
    5. Emit a one-line diagnostic banner to sys.stderr of the form
       "Re-syncing <N> registered agent(s): <comma-separated list>" so the user
       can distinguish a re-sync run from a direct connect invocation and
       see the full set of agents about to be processed
    6. Wrap the loop below in an environment override that sets $HOME to goga_home.parent
       so the internal home resolution of `connect` targets the owning installation, then
       for each recorded agent call `connect` with agents=[<name>] and that agent's
       recorded force_overwrite; remember the first non-zero result and continue with
       the remaining agents. Restore the original $HOME after the loop
    7. Return 0 when every agent re-synced, otherwise the first non-zero result

    Requirements:
    - Missing or empty registry returns 0 (normal condition, not an error)
    - Each agent re-syncs with its own recorded force_overwrite, never a hardcoded value
    - Re-sync continues after a per-agent failure and reports the first failure
    - Re-sync never runs under sudo; it operates on the home that owns `goga_home`
      (the local user by default; another user under the upgrade --user flag)
    - Override $HOME to goga_home.parent for the duration of the `connect` loop so the
      internal home resolution of `connect` targets the installation that owns
      `goga_home`; restore $HOME afterwards
    - Emit the re-sync banner ONLY when the registry contains at least one agent;
      a missing or empty registry is a silent no-op (return 0)

    Constraints:
    - Do not write connect.yml; `connect` is the single writer
    - Do not run pip or install packages; this routine only re-applies activation
    - Do not abort the whole loop on one agent failure

---

Author: Goga
CreatedAt: 28/06/26

Description: |
  Manifest describing the centralized goga skill/command/pipeline installation logic:
  assets are installed once into ~/.goga/, agents receive symlinks into ~/.goga/,
  and a per-agent connection registry is maintained at ~/.goga/connect.yml. Provides
  the shared re-sync that re-applies activation to every registered agent after a
  package change.
