"""RStack SDLC — Tau adapter (per-feature package, #552).

Contributed by Jeomon (https://github.com/Jeomon) — thank you! Originally a
single-file community Tau adapter, adopted with the bridge path updated to the
generic bin/rstack-bridge.ts and the tool surface synced to the Pi adapter
registry (the conformance contract in docs/integrations/adapter-contract.md),
then split into this per-feature package so no one file owns the whole
integration (#552). The stable entry point users configure is the sibling
`rstack_sdlc.py` shim, which loads this package and re-exports `register`.

Module map:
  params.py         the 18 sdlc_* param models + the _TOOLS registry (the
                    conformance test scans this file)
  config.py         extension-settings → RSTACK_* environment mapping
  proc.py           shared subprocess helpers (#487 process-tree kill/reap)
  bridge.py         _run_bridge/_BridgeTool — every sdlc_* call shells out to
                    `npx rstack-bridge`
  guard.py          `rstack-agents guard` enforcement (#371 fail-closed)
  gates.py          opt-in quality gates (#256)
  guarded_tools.py  _GuardedBuiltinTool shadows of write/terminal/edit
  observe.py        fire-and-forget observability events (#251/#253)
  context.py        RStack context packet fetch for the input hook (#255)
  hub.py            Business Hub companion launch
  commands.py       /sdlc slash-command surface
  capability.py     which enforcement tier THIS Tau supports (#575)
  tool_gate.py      the universal tool_call gate, installed when supported (#575)
  register.py       register(tau) wiring it all together

ENFORCEMENT TIER (#575) — read the audit below as scoped to a VERSION, not to
Tau in general. The `tool_call` findings recorded here were true at the audited
commit and remain true for every RELEASED Tau (PyPI 0.9.2), but Tau main has
since shipped the universal gate: `a35a7f0` made `Agent._before_tool_call` emit
`ToolCallEvent` for every tool, and `a8b6459` added `tool_call` to
`_INTERCEPTABLE_EVENTS` so an extension's block is finally honored instead of
discarded. That commit is in NO tag yet, and no version string distinguishes the
two (HEAD and the v0.9.2 tag both declare 0.9.2; Tau exposes no `__version__`),
so the adapter PROBES the behavior at register() time (capability.py) and:

  * universal   -> installs the tool_call gate (tool_gate.py) covering EVERY
                   tool, incl. MCP/extension tools, AND keeps the shadows;
  * shadow-only -> shadows alone, and says so on stderr, because a gate whose
                   verdict is thrown away is worse than none: it looks like
                   enforcement;
  * unknown     -> treated exactly as shadow-only (#371 applied to capability
                   detection: a probe that cannot decide never answers "capable").

The shadows below therefore remain the floor on every Tau, never traded away.

Tau (https://github.com/Jeomon/Tau) is a Python agent framework and terminal
coding assistant. This adapter was originally written against Tau's
documented `tool_call` / `before_agent_start` hooks, which promise
"block before execution" and "override the system prompt this turn"
respectively. A source audit of upstream Tau (github.com/Jeomon/Tau,
2026-07, commit 4763f38) done for issue #389 found BOTH are dead in the
real engine as of that commit — documented and fully typed, but never
wired to anything that fires them or honors their return value:

  - `tool_call`: `AgentService._before_tool_call` (tau/agent/service.py) is
    a hardcoded pass-through that never constructs a `ToolCallEvent`.
    `grep -rn "ToolCallEvent(" tau/` matches nothing anywhere in Tau's own
    codebase or test suite. Even if it fired, `tool_call` is absent from
    `ExtensionRuntime._INTERCEPTABLE_EVENTS` (tau/extensions/runtime.py), so
    the catch-all dispatcher that WOULD receive it discards every handler's
    return value (`_dispatch(...) -> None`).
  - `before_agent_start`: `BeforeAgentStartEvent` is defined in
    tau/hooks/engine.py but, like `ToolCallEvent`, is never instantiated
    anywhere. The system prompt is set once from `config.system_prompt` at
    Agent construction and never re-read per turn — there is no live path
    for a handler's returned `system_prompt` override to reach an in-flight
    turn.

On any RELEASED Tau this adapter therefore does NOT use either hook (see the
ENFORCEMENT TIER note above for what changes on a Tau that honors `tool_call`;
`before_agent_start` remains dead). It reaches the same two
goals (enforcement, context injection) through mechanisms verified live in
the same audit:

  1. Every `sdlc_*` tool shells out to the Node bridge
     (bin/rstack-bridge.ts), which reuses the existing TypeScript adapter
     and harness verbatim — no SDLC logic is reimplemented in Python.
  2. **Enforcement via built-in shadowing, not a hook.** Tau extensions may
     register a tool with the same name as a built-in to shadow it while
     loaded (docs/extensions.md: "Extension tools and commands may shadow
     built-ins while loaded... disabling or reloading the extension restores
     the previous implementation") — `ExtensionRuntime.get_tools()` resolves
     same-name collisions last-writer-wins across extensions in load order,
     and builtins load first, so a project/global/settings-listed extension
     always wins. `_GuardedBuiltinTool` wraps the real `write`/`terminal`/
     `edit` tool (via `tau.builtins.tools.create_*_tool()`, the factories that
     module documents for exactly this delegation use case), forwards its
     render hints so the TUI experience is unchanged, and calls
     `rstack-agents guard` inside `execute()` BEFORE delegating to the real
     tool — a genuine pre-execution gate, verified against a live shadowed
     call in the same audit.
  3. Observability (#251): Tau's post-execution `tool_result` hook and
     `tool_execution_failure` hook are BOTH real (verified: constructed and
     emitted from tau/engine/service.py) and feed `rstack-agents observe`,
     appending a normalized event to the active run's events.jsonl (source=
     "tau") so the Business Hub mirrors Tau activity. `_GuardedBuiltinTool`
     additionally emits a pre-execution INTENT observation for the three
     guarded tools so blocked calls still show up. All observability is
     best-effort and can never disrupt a Tau session.
  4. Full hook-event coverage (#255): Tau's `before_compaction` hook (also
     verified real, tau/agent/service.py) feeds a `context_preserved` event,
     fire-and-forget via `_observe_bg`. **Context injection uses Tau's real
     `input` hook**, not the dead `before_agent_start` — `InputEvent` is
     emitted for every submitted prompt (tau/runtime/service.py, right before
     `agent.invoke`) and a handler returning
     `InputEventResult(action="transform", text=...)` genuinely replaces the
     text the agent receives, verified live. This adapter prepends the
     RStack context packet (`rstack-agents context`) to the user's prompt
     text on every turn — the practical equivalent of Claude Code's
     UserPromptSubmit injection, achieved through the hook that actually
     fires. Best-effort with a hard timeout; returns no transform on any
     failure, so it can never block or corrupt a turn.
  5. Opt-in quality gates (#256): when the `quality_gates` extension setting
     (or RSTACK_TAU_GATES env) names presets (plan-gate/tdd-gate/scope-guard),
     `_GuardedBuiltinTool` runs each via `rstack-agents gate <name>` on the
     write/edit shadows, AFTER guard. OFF by default. Only tdd-gate ever
     blocks, and it is always overridable (RSTACK_ALLOW_NO_TESTS=1 or an
     audited approval), so a gate can never dead-end a session.

Tau events RStack does NOT wire (they do not exist in Tau's hook model):
  - There is no delegated-SUBAGENT lifecycle event. Tau's `agent_start` /
    `agent_end` are the per-prompt engine loop, not spawned specialists, so no
    `subagent_started` / `subagent_stopped` events are emitted on Tau. (On Pi
    delegation is observed directly; on Claude Code the SubagentStart/Stop hooks
    cover it.)
  - There is no NOTIFICATION event. Tau surfaces messages through its own TUI,
    so the `rstack-agents notify-hook` relay is not wired here; a Tau user who
    wants channel notifications drives them from RStack stage events instead.

Requirements on the host:
  - node + npx on PATH
  - EITHER `npm install rstack-agents` has been run once in the project
    (so `npx rstack-bridge`/`npx rstack-business` resolve locally), OR
    nothing at all — `npx --yes` fetches the published package on demand
    the first time a tool is called (slower on that first call, but no
    separate install step required). This adapter itself has no fixed
    install location of its own (no hardcoded path to the npm package),
    which is what makes it installable independently of where it sits —
    e.g. via `tau install`, once packaged (#389).

Optional configuration (settings.json → extensions.list[].settings, or env):
  worker_command   → RSTACK_WORKER_COMMAND   (Pi-compatible CLI for sdlc_delegate workers)
  default_model    → RSTACK_DEFAULT_MODEL
  escalated_model  → RSTACK_ESCALATED_MODEL
  slack_webhook    → RSTACK_SLACK_WEBHOOK
  state_dir        → RSTACK_STATE_DIR
  allow_destructive→ RSTACK_ALLOW_DESTRUCTIVE

owner: RStack developed by Richardson Gunde; Tau adapter contributed by Jeomon
"""
from __future__ import annotations

# Deliberately NO `parents[N]` path resolution anywhere in this package.
# Earlier versions resolved the package root that way, which only worked when
# the adapter lived at a fixed depth inside an npm-installed
# node_modules/rstack-agents tree — incompatible with `tau install` (a
# pip-installed package can land anywhere, e.g. a venv site-packages directory
# with no such tree at all). Every call instead shells out to the
# `rstack-bridge`/`rstack-business` BIN NAMES via `npx`, which resolves them
# the normal npm way (local node_modules/.bin, falling back to a global
# install or an on-demand registry fetch) regardless of where this Python
# package itself is sitting — this is what makes the adapter genuinely
# relocatable/installable on its own (#389).

from .register import register  # noqa: F401 — the extension entry point

# G-08 (#552): NO import-time side effects. The single-file adapter launched
# the Business Hub at module import, which meant merely importing the adapter
# (worker pools, tooling, tests) could spawn a server process and open a
# browser. The launch now happens inside register() — the actual "a Tau
# session loaded this extension" signal — so user-visible behavior in a real
# session is unchanged while a bare import stays inert.
