Skip to content
Draft
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: 4 additions & 0 deletions rust/crates/sift_cli/assets/docs/src/agents/mcp.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,10 @@ not run interactively.
| `count_test_measurements` | Count test measurements matching a filter, without fetching them. |
| `create_test_report` | Create a test report with its steps and measurements from a JSON document. |
| `append_test_measurements` | Append measurements to an existing test step. |
| `export_test_report` | Snapshot a test report's full tree to a JSON file for audit/diff and dry-runs. |
| `update_test_report` | Update a test report's fields (status, name, times, run link, archive). |
| `update_test_step` | Update a test step's fields (name, status, times, error info, metadata). |
| `update_test_measurement` | Update a test measurement's value, bounds, verdict, unit, or metadata. |
| `get_data` | Download channel data for an asset/run to a Parquet file, with optional decimation. |
| `sql` | Run SQL over one or more Parquet files; chain after `get_data` for analysis. |
| `upload_dataset` | Stream a Parquet dataset into Sift. |
Expand Down
34 changes: 33 additions & 1 deletion rust/crates/sift_cli/assets/docs/src/agents/prompts.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ then in Claude Code each prompt is available as a slash command of the form
- `/mcp__sift__explore_asset`
- `/mcp__sift__analyze_run`
- `/mcp__sift__derive_and_upload`
- `/mcp__sift__audit_test_report`

The `sift` in the command is your registered server name, so a different name
in `.mcp.json` changes the prefix accordingly. Arguments are passed positionally
Expand Down Expand Up @@ -116,4 +117,35 @@ Specify the destination explicitly:
The agent extracts the source data, applies the transform with `sql` (keeping
`timestamp_unix_nanos` as the first column, as Sift requires), confirms the
target asset, run, and any tags with you, then uploads the result with
`upload_dataset`.
`upload_dataset`.

## `audit_test_report`

Snapshots a test report (the test-results feature: a report owns steps, which
own measurements) to a JSON file for audit and diffing. Given a description of
intended edits, it dry-runs them first: it projects the changes, diffs them
against the baseline for you to confirm, and only then applies the writes and
verifies the result. Omit the edits for an audit snapshot alone.

| Argument | Required | Description |
| ------------- | -------- | ----------------------------------------------------------------- |
| `test_report` | yes | Report to audit, by id or name. |
| `changes` | no | Plain-language description of the batch edits to dry-run. |
| `output_dir` | no | Directory for the snapshot files. Defaults to the working directory. |

Snapshot a report for the record:

```
/mcp__sift__audit_test_report "nightly-regression 2024-05-01"
```

Dry-run a batch of edits before applying:

```
/mcp__sift__audit_test_report tr_abc123 "mark every step under power-on as FAILED and set its error_info"
```

The agent resolves the report, exports a baseline snapshot, projects the edits
to a second file, and diffs the two so you can confirm. Once you approve, it
applies the changes with the `update_test_*` tools, re-exports, and diffs
against the projection to confirm the server recorded what you intended.
8 changes: 8 additions & 0 deletions rust/crates/sift_cli/assets/skills/agents-md/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,14 @@ to combine them when working with Sift.
- `create_test_report`, `append_test_measurements`: create a test report with
its step/measurement tree, or add measurements to an existing step (writes —
confirm with the user first).
- `export_test_report`: snapshot a report's full tree to a JSON file (reads
only; omits server-managed fields, deterministically ordered). Use it to
record a baseline for audit/diff, or to dry-run bulk edits — export, apply
changes to a copy, diff, then run the update tools and re-export to confirm.
- `update_test_report`, `update_test_step`, `update_test_measurement`: update
fields of an existing report, step, or measurement (writes — only the fields
you set change; metadata/channel_names use replace semantics; confirm current
versus proposed values with the user first).
- `get_data`: download channel data for an asset/run to a Parquet file.
- `sql`: run SQL over one or more Parquet files (chain after `get_data`).
- `upload_dataset`: stream a Parquet dataset into Sift. Returns an
Expand Down
8 changes: 8 additions & 0 deletions rust/crates/sift_cli/assets/skills/claude-code/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,14 @@ to combine them when working with Sift.
- `create_test_report`, `append_test_measurements`: create a test report with
its step/measurement tree, or add measurements to an existing step (writes —
confirm with the user first).
- `export_test_report`: snapshot a report's full tree to a JSON file (reads
only; omits server-managed fields, deterministically ordered). Use it to
record a baseline for audit/diff, or to dry-run bulk edits — export, apply
changes to a copy, diff, then run the update tools and re-export to confirm.
- `update_test_report`, `update_test_step`, `update_test_measurement`: update
fields of an existing report, step, or measurement (writes — only the fields
you set change; metadata/channel_names use replace semantics; confirm current
versus proposed values with the user first).
- `get_data`: download channel data for an asset/run to a Parquet file.
- `sql`: run SQL over one or more Parquet files (chain after `get_data`).
- `upload_dataset`: stream a Parquet dataset into Sift. Returns an
Expand Down
64 changes: 64 additions & 0 deletions rust/crates/sift_mcp/src/prompt/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,13 @@ pub struct DeriveAndUploadArgs {
target_run: Option<String>,
}

#[derive(Debug, Deserialize, JsonSchema)]
pub struct AuditTestReportArgs {
test_report: String,
changes: Option<String>,
output_dir: Option<String>,
}

#[prompt_router(vis = "pub(crate)")]
impl SiftMcpServer {
#[prompt(
Expand Down Expand Up @@ -157,4 +164,61 @@ impl SiftMcpServer {

vec![PromptMessage::new_text(PromptMessageRole::User, body)]
}

#[prompt(
name = "audit_test_report",
description = "Snapshot a test report for audit, and optionally dry-run a batch of edits: diff the projected changes before applying, then apply and verify. Writes only after you confirm the diff."
)]
pub async fn audit_test_report(
&self,
params: Parameters<AuditTestReportArgs>,
) -> Vec<PromptMessage> {
let Parameters(AuditTestReportArgs {
test_report,
changes,
output_dir,
}) = params;

let dir = output_dir.unwrap_or_else(|| "the current working directory".to_string());

let tail = match changes {
Some(changes) => format!(
"The user wants to dry-run this batch of edits before applying them: \"{changes}\".\n\n\
4. DRY RUN: apply the intended edits to a COPY of the baseline file (write \
`<dir>/<test_report_id>.projected.json`). Do NOT call any write tool yet. Field names in \
the snapshot mirror the update-tool inputs, so map each edit to the step or measurement \
by its id. Remember `metadata` and `channel_names` are REPLACE, and changing a \
measurement value does not recompute `passed` — set it explicitly if the verdict should \
change.\n\
5. Diff baseline vs projected and show the user exactly what would change. Get explicit \
confirmation before writing.\n\
6. On approval, apply the edits with `update_test_report`, `update_test_step`, and \
`update_test_measurement`, using the ids from the snapshot.\n\
7. Re-export to `<dir>/<test_report_id>.after.json` and diff it against the projected \
file. Report any discrepancy between what was intended and what the server recorded."
),
None => {
"This is an audit snapshot only. Report the baseline file path as the record and \
make no changes."
.to_string()
}
};

let body = format!(
"Help the user audit a Sift test report (the test-results feature: a report owns steps, which \
own measurements). The user referred to the report as: \"{test_report}\".\n\n\
Write snapshot files under: {dir}.\n\n\
Steps:\n\
1. Resolve the report with `list_test_reports`. If \"{test_report}\" looks like an id, filter \
`test_report_id == \"{test_report}\"`; otherwise filter `name == \"{test_report}\"`. If \
several match, ask the user which one before continuing.\n\
2. Snapshot the baseline with `export_test_report` to \
`<dir>/<test_report_id>.before.json`. This is the audit record; it omits server-managed \
fields and is deterministically ordered, so diffs show only user-editable changes.\n\
3. Confirm with the user what the baseline captured (step and measurement counts).\n\
{tail}"
);

vec![PromptMessage::new_text(PromptMessageRole::User, body)]
}
}
Loading