Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion INTEGRATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ This plugin is built so the **MCP core is truly cross-client** and the **harness
layer ports via the cross-vendor Agent Skills standard**. Only one piece is
genuinely Claude-Code-specific (the batch *dynamic workflow*); on Cursor and
Codex that role is filled by the sequential harness or subagents. Every path is
autonomous after the single `/rca-build` gate — no host ever prompts mid-run.
autonomous after the single `/rca-build` gate — no host ever prompts mid-run. The
setup interview is a phase of that same skill: it runs on a repo's first invocation
and never again, so it is the one interactive surface and it is not per build.

## What transfers, what doesn't

Expand Down
61 changes: 37 additions & 24 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@ RCA in the TRA (Test Observability) dashboard.

> **The full RCA report lives on the Test Observability UI, not in Claude.**
> The plugin surfaces a terse glimpse, triggers the dashboard report
> (`triggerRcaReport`), and prints the link. It **discovers and delegates** to
> the infra skills/tools already in your client (GitHub, whatever runtime you have — k8s/ECS/docker/… — kibana/other
> logs, metrics). It does **not** install or own those connectors, and it never
> writes a local report file.
> (`triggerRcaReport`), and prints the link. It **learns and delegates**: the setup
> interview records which of your own tools serves each capability — code, runtime,
> logs, metrics, CI — and every run after that uses them. It does **not** install or
> own those connectors, ship a list of supported ones, or write a local report file.

## Install

Expand Down Expand Up @@ -51,22 +51,36 @@ discovery, deeplink) is in **[INTEGRATION.md](INTEGRATION.md)**.
Args: a build id (bare, `build_id=`, or a dashboard link) plus optional PR URLs
/ repo hints.

## The single gate

The run has exactly **one gate** before execution, with two parts:

1. **Connector discovery + validation** — every connector relevant to test RCA
(github, infra, logs, metrics, …) is enumerated and probe-validated (`gh auth
status`, an infra probe matching whatever runtime exists — kubectl/docker/ecs/… — MCP tools listed). The result is a validated
capability manifest: `connector → valid | invalid | absent`. A gap is
recorded and declared to the TFA agent ("I don't have logs/metrics access") —
never a blocker.
2. **Requirements** — intake fields (product repo, automation repo, branches,
PRs in play, build id) are resolved **by assumption** wherever possible
(invocation args, `gh repo view`, current branch). At most **one**
consolidated question may be asked at gate close, and only for genuinely
non-assumable, load-bearing fields. Headless (`claude -p`) never asks: a
missing build id fails fast; everything else is a recorded gap.
## First contact, then the single gate

**The first time you run this in a repo, it interviews you.** It says what
BrowserStack already has versus what only you can supply, then walks capability by
capability — GitHub first, then your application logs, CI, whatever runs your
services, your metrics — asking only for the scope it cannot see and **verifying
every answer with a live read** before keeping it. The result is committed to
`.rca-context.json`, so a teammate who clones the repo inherits it and is asked only
for credentials.

**GitHub is the one hard requirement.** Without the code and the PRs merged into the
branch under test there is no culprit PR to name, and that is the deliverable — so
setup blocks there and says so. Everything else is suggested, never forced: skip it
and it becomes a recorded gap that shows up in the report.

Nothing here ships a list of supported vendors. No fingerprints, no probe table: the
model works out that a given CLI is your runtime or that a given MCP server is your
metrics, which is why a stack nobody here has heard of works with no code change.

**Every run after that has exactly one gate**, with two parts:

1. **Capability validation** — each capability the context recorded is re-verified
by replaying the read that proved it, all in one batch, into a manifest:
`capability → valid | invalid | absent`. An unverifiable GitHub refuses the run.
Anything else is a recorded gap, declared to the TFA agent ("I don't have
logs/metrics access") — never a blocker.
2. **Intake** — product repo, automation repo, branches, PRs in play, build id,
resolved in a fixed precedence: build metadata → invocation args → the persisted
profile → connector defaults → inference. At most **one** consolidated question
at gate close, and only for a field that is both non-assumable and load-bearing.

**After the gate closes, the run never asks you anything again** — RCA
execution is fully autonomous. Evidence gaps degrade to "unavailable" back to
Expand Down Expand Up @@ -103,10 +117,9 @@ Point it at any red BrowserStack build — the harness discovers what it needs:
/rca-build <build-id>
```

The single gate validates connectors (github via `gh`, infra via whatever
runtime connector exists — kubectl/docker/ecs/…) and resolves the intake
**by inference** (product/automation repo from the cwd's git remote, branches,
any PRs you pass). It never assumes a product repo from unrelated workspace
The gate re-validates the capabilities your context recorded and resolves the
intake, preferring the build's own metadata and your committed profile over
inference. It never assumes a product repo from unrelated workspace
docs — if it can't infer one that matches the failures, it records the gap and
proceeds RCA-only rather than blaming the wrong repo. Then it clusters the
failures, drives `tfaRcaTurn` per cluster, and lands per-test RCAs on the
Expand Down
63 changes: 51 additions & 12 deletions agents/ai-tfa-coordinator.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ read-only and has no side effects, so a read is always safe to repeat.
shares digested findings; the cache shares raw call results. Given
`buildId` and your own `testRunId` as `writerId`:

- **Shell (`gh`/`kubectl`/`curl`/`git`)** — prefix the fetch with the
- **Shell (any read-only command — the forge CLI, a runtime CLI, `curl`, `git`)** — prefix the fetch with the
wrapper; it behaves exactly like the raw command (same stdout, same exit
code) but only executes on a miss:
`node <pluginRoot>/bin/cached-exec.mjs <buildId> <testRunId> '<command>'`
Expand All @@ -142,14 +142,52 @@ read-only and has no side effects, so a read is always safe to repeat.
Skip caching for one-off queries only this test needs.
- **NEVER cache `tfaRcaTurn` / `getTfaTurnResult` / `triggerRcaReport`** —
they are stateful, and the cache refuses them outright.
- Don't re-probe a connector the gate already validated (`gh auth status`,
`kubectl version`); the manifest above is the answer.
- Two wrapper gotchas: **(i)** hit/miss banners go to stderr — don't
`2>&1 | jq` (merges banner into pipe). **(ii)** commands containing single
quotes can't nest inside a single-quoted argument; pipe on stdin instead:
- Don't re-probe a connector the gate already validated; the manifest above is
the answer, and it records what proved each one.
- Hit/miss banners go to stderr. `2>/dev/null` if you don't want them; see
§ You clean up what you create before you redirect them to a file.
- Two more wrapper gotchas: **(i)** don't `2>&1 | jq` — that merges the banner
into the pipe, which is why the banner is on stderr in the first place.
**(ii)** commands containing single quotes can't nest inside a single-quoted
argument; pipe on stdin instead:
`printf '%s' '<command>' | node .../cached-exec.mjs <buildId> <writerId> -`.
A pipe belongs outside the wrapper.

**Scratch goes in your own directory, and you delete what you create.**

Your cwd is the CUSTOMER's working directory, and every coordinator in this run
shares it. Never write scratch there.

Prefer holding a fetched file in context — the tool cache already dedupes the
fetch, so a second copy on disk buys nothing. When you genuinely need one (a
response too large to hold, a message worth re-reading), put it in the directory
that is yours alone:

```
node -e 'import("<pluginRoot>/lib/state-dir.mjs").then(m =>
console.log(m.scratchDirFor("<buildId>", "<yourTestRunId>")))'
```

Keyed on your own id, so no other agent can collide with you however you name a
file inside it — and it sits beside the CSV and the tool cache, where run state
already lives and the OS reclaims it, rather than in anyone's repo.

**Then delete what you created, by name, before you finish.** Not a glob, not a
sweep, not "tidy the directory": you are the only party that knows which paths
you wrote, which is why this cannot be handed to the orchestrator or a later
step. **The plugin never deletes a file it did not create** — it runs on
someone's machine, so a wildcard would take their files with yours. Your own
directory makes that safe to get right; it does not excuse skipping it.

One real run left 28 files and 572 KB in a customer's repo root — fetched
sources, saved diffs, raw API responses, redirected stderr, a drafted message.
Several coordinators had independently chosen the same short names, so they were
overwriting each other as well as littering. Nothing referenced any of it: the
findings live in the CSV rows, the evidence shards and the dashboard report.

If a file must outlive your turn, name its path in your `RCA_OUTPUT` block so the
orchestrator knows it is deliberate rather than residue.

**Never read an empty `prsInWindow` as "no PRs in the window."** An empty
list means "no PRs" ONLY when the entry also has `prsSearched: true`.
Check `coverage.reposWithUntrustedPrList` (or call
Expand Down Expand Up @@ -387,8 +425,10 @@ Notes:
in `root_cause`.
- `asks_skipped` always includes `test_logs` whenever TFA asked for logs.
`asks_fulfilled` **never** includes `test_logs`.
- `asks_unavailable` is the evidence-coverage signal the coverage stamp turns
into a confidence band.
- `asks_unavailable` is the evidence-coverage signal: it records what could not be
gathered so a RESOLVED RCA built with infra, logs and metrics all unavailable does
not read like one built on full evidence. Report it accurately and completely —
the dashboard is what weighs it. There is no local confidence stamp to compute.
- `failed` is the no-parseable-result / no-input case; the orchestrator
synthesizes a `failed` row if this coordinator dies — keep the block valid.

Expand All @@ -404,10 +444,9 @@ Notes:
- **Never** let drain reads consume the turn cap, and never drain past the
`softPendingDrain` budget — a wedged turn must not hang the batch.
- **Never** dump raw logs, full diffs, or full file contents into a turn message — digest only.
- **Never** run an unfiltered gather call (a bare `gh api ...` with no `--jq`,
`kubectl get ... -o wide`/`-o yaml` when a narrower `-o custom-columns`
answers the ask) — project to the needed field(s) before the call runs, not
by reading past the noise after.
- **Never** run an unfiltered gather call — a bare `gh api ...` with no `--jq`, or
any tool's full-object output when a narrower projection answers the ask. Project
to the needed field(s) before the call runs, not by reading past the noise after.
- **Never** write to any repo / cluster / ticket / the run — every action is read-only.
- **Never** editorialize a cause — pass TFA's `glimpse` through verbatim.
- **Never** blindly inherit a representative's cause for a sibling — confirm against its own logs.
Expand Down
Loading