Extending phax

Extend phax

Four hooks let your own tools inform a run. Each is a command in phax.json, split on spaces and run without a shell, that reads a JSON request on stdin and answers JSON on stdout.

Diagnostics gate steps

A gate step with "output": "diagnostics" prints a JSON document instead of a log, and phax reads its verdict from that document rather than from its exit code:

{
  "diagnostics": [
    {
      "rule": "no-cycles",
      "class": "invariant",
      "location": { "file": "src/a.ts", "line": 3 },
      "message": "…",
      "repair": "…"
    }
  ]
}

The step must print the document every time it runs, { "diagnostics": [] } when it passes; empty or non-JSON output counts as a missing document and fails the step, even on exit 0. An invariant finding fails the step. A completion finding names the scopes it belongs to, and fails the step only once all of them are closed according to your scope provider; until then it is pending, shown to the agent as optional work. The failing findings, not the raw log, are what the agent is asked to fix.

Orient provider

{ "orient": { "command": "node ./orient.mjs" } }

Rules or notes attached to parts of your codebase, which phax weaves into each phase's prompt for the files it plans to touch. phax asks {"files": [...]} and expects {"rows": [{"id", "title", "severity", "trigger"}]}; it asks {"expand": "<id>"} and expects {"row": {..., "body"}} or {"row": null}. The agent can call phax orient during the phase. Full contract: phax orient.

Scope provider

{ "scopes": { "command": "node ./scopes.mjs" } }

Answers which scopes are closed, for completion findings. Before each gate that has a diagnostics step, except the last phase's (which closes every scope), phax sends the phase and every phase's planned files:

{
  "phase": "phase-02",
  "phases": [
    { "id": "phase-01", "files": ["src/core/billing/port.ts"] },
    { "id": "phase-02", "files": ["src/core/billing/invoice.ts"] }
  ]
}

and expects {"closed": ["<scope>", ...]}. A completion finding with no scope provider configured, or a provider that fails, fails the gate with the reason.

Plan auditor

{ "planAuditor": { "command": "node ./audit-plan.mjs" } }

Reviews a plan's shape for phax plans lint (never during a run). It receives every phase's planned files, {"phases": [{"id", "files"}]} — nothing else leaves phax — and answers {"findings": [{"message", "phases": [...]}]}. Its findings are warnings; a provider that fails or takes more than 30 seconds becomes one warning saying why.

examples/hello-world/ has a small example of each.