canary-test-cli 7.1.0 → 7.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/agents/skills/README.md +308 -0
- package/agents/skills/canary:generate.md +49 -0
- package/agents/skills/canary:init.md +37 -0
- package/agents/skills/canary:migrate.md +66 -0
- package/agents/skills/claude-code/canary-add-framework/SKILL.md +248 -0
- package/agents/skills/claude-code/canary-blackhawk/SKILL.md +170 -0
- package/agents/skills/claude-code/canary-blackhawk/scripts/cli.mjs +188 -0
- package/agents/skills/claude-code/canary-blackhawk/scripts/rules.mjs +120 -0
- package/agents/skills/claude-code/canary-blackhawk/scripts/scanner.mjs +244 -0
- package/agents/skills/claude-code/canary-blackhawk/scripts/string-literals.mjs +116 -0
- package/agents/skills/claude-code/canary-cassandra/SKILL.md +180 -0
- package/agents/skills/claude-code/canary-cassandra/scripts/cli.mjs +268 -0
- package/agents/skills/claude-code/canary-cassandra/scripts/engine.mjs +95 -0
- package/agents/skills/claude-code/canary-ci-ready/SKILL.md +161 -0
- package/agents/skills/claude-code/canary-ci-ready/skill.yaml +14 -0
- package/agents/skills/claude-code/canary-company-knowledge/SKILL.md +196 -0
- package/agents/skills/claude-code/canary-critical-areas/SKILL.md +142 -0
- package/agents/skills/claude-code/canary-critical-areas/skill.yaml +16 -0
- package/agents/skills/claude-code/canary-edge-case-discovery/SKILL.md +160 -0
- package/agents/skills/claude-code/canary-edge-case-discovery/skill.yaml +16 -0
- package/agents/skills/claude-code/canary-fail-fast/SKILL.md +75 -0
- package/agents/skills/claude-code/canary-fail-fast/scripts/cli.mjs +118 -0
- package/agents/skills/claude-code/canary-fail-fast/scripts/digest.mjs +69 -0
- package/agents/skills/claude-code/canary-fail-fast/scripts/failures.mjs +60 -0
- package/agents/skills/claude-code/canary-fail-fast/scripts/fastfail_check.mjs +43 -0
- package/agents/skills/claude-code/canary-fail-fast/scripts/parse.mjs +149 -0
- package/agents/skills/claude-code/canary-failure-impact/SKILL.md +153 -0
- package/agents/skills/claude-code/canary-failure-impact/skill.yaml +15 -0
- package/agents/skills/claude-code/canary-fleet-health/SKILL.md +196 -0
- package/agents/skills/claude-code/canary-generate-test/SKILL.md +185 -0
- package/agents/skills/claude-code/canary-instrument/SKILL.md +157 -0
- package/agents/skills/claude-code/canary-instrument/scripts/cli.mjs +178 -0
- package/agents/skills/claude-code/canary-instrument/scripts/otel_bootstrap/instrument.mjs +96 -0
- package/agents/skills/claude-code/canary-instrument/scripts/otel_bootstrap/playwright-fixture.ts +44 -0
- package/agents/skills/claude-code/canary-instrument/scripts/run_types.mjs +81 -0
- package/agents/skills/claude-code/canary-instrument/scripts/span_reader.mjs +187 -0
- package/agents/skills/claude-code/canary-katana/SKILL.md +243 -0
- package/agents/skills/claude-code/canary-katana/scripts/alarm.mjs +296 -0
- package/agents/skills/claude-code/canary-katana/scripts/cli.mjs +247 -0
- package/agents/skills/claude-code/canary-katana/scripts/diffscan.mjs +0 -0
- package/agents/skills/claude-code/canary-katana/scripts/ledger.mjs +183 -0
- package/agents/skills/claude-code/canary-pr-guardian/SKILL.md +129 -0
- package/agents/skills/claude-code/canary-pr-guardian/skill.yaml +17 -0
- package/agents/skills/claude-code/canary-promote-test/SKILL.md +228 -0
- package/agents/skills/claude-code/canary-savant/SKILL.md +233 -0
- package/agents/skills/claude-code/canary-savant/scripts/cli.mjs +274 -0
- package/agents/skills/claude-code/canary-savant/scripts/restoration.mjs +274 -0
- package/agents/skills/claude-code/canary-savant/scripts/rules.mjs +168 -0
- package/agents/skills/claude-code/canary-savant/scripts/runner.mjs +572 -0
- package/agents/skills/claude-code/canary-savant/scripts/scanner.mjs +374 -0
- package/agents/skills/claude-code/canary-savant/scripts/string-literals.mjs +116 -0
- package/agents/skills/claude-code/canary-setup-harness/SKILL.md +263 -0
- package/agents/skills/claude-code/canary-shadow/SKILL.md +131 -0
- package/agents/skills/claude-code/canary-shadow/scripts/cases.example.json +32 -0
- package/agents/skills/claude-code/canary-shadow/scripts/cli.mjs +195 -0
- package/agents/skills/claude-code/canary-ship/SKILL.md +177 -0
- package/agents/skills/claude-code/canary-ship/skill.yaml +16 -0
- package/agents/skills/claude-code/canary-strix/SKILL.md +130 -0
- package/agents/skills/claude-code/canary-strix/scripts/cli.mjs +255 -0
- package/agents/skills/claude-code/canary-strix/scripts/scanner.mjs +252 -0
- package/agents/skills/claude-code/canary-strix/scripts/terms.mjs +132 -0
- package/agents/skills/claude-code/canary-test-pipeline/SKILL.md +159 -0
- package/agents/skills/claude-code/canary-test-pipeline/skill.yaml +19 -0
- package/agents/skills/claude-code/canary-test-reporter/SKILL.md +138 -0
- package/agents/skills/claude-code/canary-test-reporter/scripts/cli.mjs +98 -0
- package/agents/skills/claude-code/canary-test-reporter/scripts/json_report.mjs +58 -0
- package/agents/skills/claude-code/canary-test-reporter/scripts/parse.mjs +216 -0
- package/agents/skills/claude-code/canary-test-reporter/scripts/render.mjs +114 -0
- package/agents/skills/lib/parse-args.mjs +214 -0
- package/dist/engine/cli-commands.js +3 -65
- package/dist/engine/core/quality-scorer.js +15 -2
- package/dist/engine/core/skill-dispatch.js +115 -0
- package/dist/engine/core/skill-examples.js +103 -3
- package/dist/engine/core/skill-registry.js +59 -4
- package/dist/engine/core/test-files.js +77 -0
- package/dist/engine/core/vacuity-scanner.js +184 -14
- package/dist/engine/guardian/analysis-emit.js +7 -2
- package/dist/engine/guardian/cli.js +121 -9
- package/dist/engine/guardian/pr-check.js +317 -18
- package/dist/engine/skills-cli.js +123 -19
- package/dist/reporters/testtracker.d.ts +1 -1
- package/dist/reporters/testtracker.js +1 -1
- package/package.json +3 -2
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: canary-fleet-health
|
|
3
|
+
description: >
|
|
4
|
+
Fleet-wide test health summary across suites — flaky tests, failure spikes,
|
|
5
|
+
cross-suite common failures, and regression candidates from the run-history
|
|
6
|
+
store. Use when the user asks "how healthy is our test fleet", "fleet-wide
|
|
7
|
+
flake report", "any regressions this week", "failure spikes across suites", or
|
|
8
|
+
"canary analyze". Produces one compact, scannable summary — not a dashboard.
|
|
9
|
+
NOT for diagnosing a single known-flaky test (canary-flake-hunter) or scoring
|
|
10
|
+
one suite's CI readiness (canary-ci-ready).
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
# Canary: Fleet Health
|
|
14
|
+
|
|
15
|
+
Wraps `canary analyze` (fleet-wide flake/spike/regression analytics) and the
|
|
16
|
+
run-history store to answer "how's the whole fleet doing?" in one chat-turn
|
|
17
|
+
summary. Today, [`canary-flake-hunter`](../../../canary-flake-hunter.md) only
|
|
18
|
+
diagnoses a single test you already suspect is flaky — this skill is the
|
|
19
|
+
fleet-wide counterpart: it tells you _where to look_ before you reach for the
|
|
20
|
+
hunter.
|
|
21
|
+
|
|
22
|
+
Per the adoption audit this skill implements (candidate #10), this is
|
|
23
|
+
deliberately a **low-cost validation step**: a compact text summary a human can
|
|
24
|
+
scan in one turn, not a dashboard or visual surface. If fleet-wide analytics
|
|
25
|
+
prove valuable, a richer surface is a separate, larger investment — don't
|
|
26
|
+
over-build this one.
|
|
27
|
+
|
|
28
|
+
## When to Use
|
|
29
|
+
|
|
30
|
+
- Weekly/periodic health check: "how's the test fleet looking?"
|
|
31
|
+
- Before a release: "any regressions or spikes we should know about?"
|
|
32
|
+
- Triaging where to spend test-maintenance effort across many suites
|
|
33
|
+
- NOT for a single test you already know is flaky — use
|
|
34
|
+
[`canary-flake-hunter`](../../../canary-flake-hunter.md) to diagnose root
|
|
35
|
+
cause and propose a fix
|
|
36
|
+
- NOT for scoring one suite's CI readiness — use
|
|
37
|
+
[`canary-ci-ready`](../canary-ci-ready/SKILL.md) (coverage depth, assertion
|
|
38
|
+
quality, runtime for _one_ suite)
|
|
39
|
+
- NOT a substitute for `.canary/critical-areas.json` risk ranking — use
|
|
40
|
+
[`canary-critical-areas`](../canary-critical-areas/SKILL.md) for code-level
|
|
41
|
+
risk, this skill is history-data-level health
|
|
42
|
+
|
|
43
|
+
## Process
|
|
44
|
+
|
|
45
|
+
### Phase 1: RESOLVE THE STORE
|
|
46
|
+
|
|
47
|
+
Fleet analytics read from the run-history store, not from a live test run.
|
|
48
|
+
Before running anything, confirm data exists:
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
canary history summary <suite> --runs 1
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
- **Configured store:** `CANARY_HISTORY_DB_URL` env var (Supabase-backed) or
|
|
55
|
+
falls back to the local NDJSON file at
|
|
56
|
+
`test-results/reports/history-v2.jsonl`.
|
|
57
|
+
- **No local file and no `CANARY_HISTORY_DB_URL`:** there is nothing to analyze
|
|
58
|
+
yet. Say so plainly — "No run history found. Push results with
|
|
59
|
+
`canary history push` after a CI run, or run `canary history migrate` if you
|
|
60
|
+
have v1 history.jsonl data." Do not fabricate a health summary from nothing.
|
|
61
|
+
|
|
62
|
+
### Phase 2: RUN THE RELEVANT ANALYSES
|
|
63
|
+
|
|
64
|
+
Default to the combined digest unless the user asked about one specific
|
|
65
|
+
dimension:
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
canary analyze digest --json
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
If the user asked about one thing specifically, run only that subcommand instead
|
|
72
|
+
of the full digest — cheaper and more focused:
|
|
73
|
+
|
|
74
|
+
| User asks about | Command |
|
|
75
|
+
| --------------------------------------- | -------------------------------------------------------------------- |
|
|
76
|
+
| Flaky tests fleet-wide | `canary analyze flaky --window-runs 30 --min-rate-pct 10 --json` |
|
|
77
|
+
| Failure spikes | `canary analyze spikes --delta-pp 20 --json` |
|
|
78
|
+
| Cross-suite common failures | `canary analyze common-failures --min-suites 2 --json` |
|
|
79
|
+
| Newly broken tests after a green streak | `canary analyze regression-candidates --json` |
|
|
80
|
+
| "Area health" / degrading areas | See the caveat below — this dimension does not currently return data |
|
|
81
|
+
|
|
82
|
+
**Known limitation — be upfront about it:** `canary analyze area-health` (and
|
|
83
|
+
the `area_health` section of `digest`) is currently wired to an empty data set
|
|
84
|
+
in `ts/src/analysis/cli.ts` / `ts/src/analysis/engine.ts` — it always reports
|
|
85
|
+
"No area health data available," regardless of history. Don't present this as a
|
|
86
|
+
working check; tell the user area-degradation tracking isn't implemented yet
|
|
87
|
+
rather than silently omitting it.
|
|
88
|
+
|
|
89
|
+
**Store-type caveat:** `flaky` queries the store directly and works with either
|
|
90
|
+
backend. `spikes`, `common-failures`, and `regression-candidates` currently only
|
|
91
|
+
populate fully when the backing store is the local NDJSON file (`AnalysisEngine`
|
|
92
|
+
special-cases `LocalHistoryStore` for suite discovery and per-test aggregation)
|
|
93
|
+
— with a Supabase-backed store (`CANARY_HISTORY_DB_URL` set), those three may
|
|
94
|
+
come back empty even with real history. If the digest shows all-zero
|
|
95
|
+
spikes/common-failures/ regressions _and_ `CANARY_HISTORY_DB_URL` is set, flag
|
|
96
|
+
this as a likely store-support gap, not a clean bill of health.
|
|
97
|
+
|
|
98
|
+
### Phase 3: CONDENSE TO ONE SCREEN
|
|
99
|
+
|
|
100
|
+
Don't paste raw Markdown tables from the CLI — they're built for file artifacts,
|
|
101
|
+
not chat. Pull the top 3–5 rows per section and compress to the Output Format
|
|
102
|
+
below. If a section is empty, say "none" in one line; don't render an empty
|
|
103
|
+
table.
|
|
104
|
+
|
|
105
|
+
### Phase 4: SURFACE THE ONE ACTIONABLE THING
|
|
106
|
+
|
|
107
|
+
Look across sections for correlation — e.g., a suite with both a recent spike
|
|
108
|
+
and several tests over the flake threshold is a stronger signal than either
|
|
109
|
+
alone. Call that out explicitly as the suggested next step, and name the
|
|
110
|
+
specific downstream skill:
|
|
111
|
+
|
|
112
|
+
- Single suspicious test → point at
|
|
113
|
+
[`canary-flake-hunter`](../../../canary-flake-hunter.md)
|
|
114
|
+
- Whole suite trending down → point at
|
|
115
|
+
[`canary-ci-ready`](../canary-ci-ready/SKILL.md) for that suite
|
|
116
|
+
- Systemic cross-suite pattern (e.g. the same connection error in 3 suites) →
|
|
117
|
+
this is infrastructure/environment, not a test bug; say so instead of
|
|
118
|
+
suggesting a test fix
|
|
119
|
+
|
|
120
|
+
## Output Format
|
|
121
|
+
|
|
122
|
+
```text
|
|
123
|
+
Fleet Health — window: 30 runs
|
|
124
|
+
|
|
125
|
+
Flaky (≥10%): 3 tests top: checkout_retry_test (32%, api suite)
|
|
126
|
+
Spikes (≥20pp): 1 suite e2e_ui +25pp since 2026-07-10
|
|
127
|
+
Area health: not available (not yet implemented — always empty)
|
|
128
|
+
Common failures: 1 pattern "ECONNREFUSED 127.0.0.1:5432" across 2 suites
|
|
129
|
+
Regressions: 2 tests orders_post_201 broke after 12-run green streak
|
|
130
|
+
|
|
131
|
+
Suggested next step: e2e_ui's spike + 2 of the 3 flaky tests are in that
|
|
132
|
+
suite — investigate the suite before chasing individual flakes.
|
|
133
|
+
Run /canary-ci-ready on e2e_ui, or /canary-flake-hunter on
|
|
134
|
+
checkout_retry_test for a root-cause fix.
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
Keep it to one screen. Omit a line entirely rather than padding with "no data"
|
|
138
|
+
noise for sections that were never requested.
|
|
139
|
+
|
|
140
|
+
## Flags
|
|
141
|
+
|
|
142
|
+
- `--window-runs <runs>` — rolling window measured in RUNS, not days (default:
|
|
143
|
+
30, passed through to `canary analyze`)
|
|
144
|
+
- `--min-rate-pct <percent>` — minimum flake rate to report, on a 0-100 percent
|
|
145
|
+
scale (default: 10, so `10` means 10%, not 0.1)
|
|
146
|
+
- `--delta-pp <points>` — spike threshold as a PERCENTAGE-POINT rise in failure
|
|
147
|
+
rate (default: 20, so `20` means 5% → 25%, not 5% → 6%)
|
|
148
|
+
- `--suite <name>` — scope to one suite instead of the whole fleet
|
|
149
|
+
- `--json` — pass through the underlying CLI's `--json` when the caller wants
|
|
150
|
+
structured data instead of a chat summary
|
|
151
|
+
|
|
152
|
+
The unitless spellings `--window`, `--delta`, and `--min-rate` are deprecated
|
|
153
|
+
aliases: they still work and take the same values, but they print a note on
|
|
154
|
+
stderr naming the replacement. Prefer the unit-bearing names in anything you
|
|
155
|
+
write down.
|
|
156
|
+
|
|
157
|
+
## Error Handling
|
|
158
|
+
|
|
159
|
+
| Situation | What To Do |
|
|
160
|
+
| ------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
161
|
+
| No history data at all (fresh repo) | Say so; point at `canary history push` / `canary history migrate`. Don't run the analysis commands against nothing. |
|
|
162
|
+
| `CANARY_HISTORY_DB_URL` set but Supabase unreachable | `make_store` only guards against a missing `agent.history.supabase_store` import, not a live connection failure — a query error will surface as a CLI exception. Report the exception text, suggest checking connectivity/credentials, don't retry silently. |
|
|
163
|
+
| `area-health` requested explicitly | Explain the known limitation (Phase 2) rather than showing an empty table with no context. |
|
|
164
|
+
| Digest looks suspiciously all-zero with `CANARY_HISTORY_DB_URL` set | Flag the store-type caveat (Phase 2) before concluding the fleet is healthy. |
|
|
165
|
+
| User wants a specific suite that has no history | `canary history summary <suite>` returns `total_runs: 0` — report that directly instead of running the full fleet analysis. |
|
|
166
|
+
|
|
167
|
+
## Examples
|
|
168
|
+
|
|
169
|
+
### Example: Weekly health check with a correlated signal
|
|
170
|
+
|
|
171
|
+
**Prompt:** "How's the fleet looking this week?"
|
|
172
|
+
|
|
173
|
+
**Action:** Confirm local history file exists. Run `analyze digest --json`.
|
|
174
|
+
Condense: 3 flaky tests (2 in `e2e_ui`), 1 spike (`e2e_ui`, +25pp), no common
|
|
175
|
+
failures, 2 regression candidates. Correlate: `e2e_ui` shows up in both flaky
|
|
176
|
+
and spikes — call it out as the priority, suggest `canary-ci-ready` on `e2e_ui`
|
|
177
|
+
before chasing the individual flaky tests.
|
|
178
|
+
|
|
179
|
+
### Example: No history yet
|
|
180
|
+
|
|
181
|
+
**Prompt:** "Give me a fleet health summary."
|
|
182
|
+
|
|
183
|
+
**Action:** `history summary api --runs 1` returns `total_runs: 0` and no local
|
|
184
|
+
NDJSON file exists. Report plainly: no run history is available yet; point at
|
|
185
|
+
`canary history push` after the next CI run. Do not run `analyze digest` against
|
|
186
|
+
an empty store and present an empty report as "all clear" — absence of data is
|
|
187
|
+
not evidence of health.
|
|
188
|
+
|
|
189
|
+
## Related Skills
|
|
190
|
+
|
|
191
|
+
- [`canary-flake-hunter`](../../../canary-flake-hunter.md) — single-test
|
|
192
|
+
root-cause diagnosis once fleet health points at a candidate
|
|
193
|
+
- [`canary-ci-ready`](../canary-ci-ready/SKILL.md) — single-suite CI readiness
|
|
194
|
+
scoring (coverage, assertions, runtime)
|
|
195
|
+
- [`canary-critical-areas`](../canary-critical-areas/SKILL.md) — code-level risk
|
|
196
|
+
ranking, a different signal from history-based health
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: canary-generate-test
|
|
3
|
+
description: >
|
|
4
|
+
Generate a framework-appropriate test from a natural-language requirement by
|
|
5
|
+
routing through Canary's classify → recommend → generate pipeline (the
|
|
6
|
+
`/canary-write-test` slash command), writing the test under `tests/generated/`
|
|
7
|
+
and optionally executing it. Use for "write a test for X", "I need an API test
|
|
8
|
+
that does Y", "scaffold a new test from this description", or triaging a bug
|
|
9
|
+
report into a regression test — when a CLI batch pipeline run (not an
|
|
10
|
+
interactive session-generated file) is what's wanted. See
|
|
11
|
+
`agents/canary-test-author.md` (interactive, session-generated, wired to
|
|
12
|
+
`/canary-write-test`) and `agents/canary-test-generator.md` (MCP
|
|
13
|
+
write_test_file retry loop) for the other two "write a test" paths. Not for
|
|
14
|
+
editing existing tests or choosing between frameworks abstractly.
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
# Canary: Generate Test
|
|
18
|
+
|
|
19
|
+
> Generate a framework-appropriate test from a natural-language requirement.
|
|
20
|
+
> Routes through Canary's classify → recommend → generate pipeline, writes the
|
|
21
|
+
> test under `tests/generated/`, and optionally executes it.
|
|
22
|
+
|
|
23
|
+
## When to Use
|
|
24
|
+
|
|
25
|
+
- When the user asks to scaffold a new test from a natural-language description
|
|
26
|
+
("write a test for X", "I need an API test that does Y")
|
|
27
|
+
- When triaging a bug report into a regression test
|
|
28
|
+
- When extending an existing test suite with a new case but the framework choice
|
|
29
|
+
is ambiguous
|
|
30
|
+
- NOT for editing existing tests — use the project's normal edit flow
|
|
31
|
+
- NOT for choosing between frameworks abstractly — use the framework-registry
|
|
32
|
+
docs, not this skill
|
|
33
|
+
- NOT for running existing tests — use the framework's CLI directly
|
|
34
|
+
|
|
35
|
+
### Relative to the other "write a test" paths
|
|
36
|
+
|
|
37
|
+
This skill is the **batch generation path** (the `/canary-write-test` slash
|
|
38
|
+
command) — use it for scripted/CI-driven generation runs consumed
|
|
39
|
+
programmatically from `tests/generated/`. For an interactive, session-generated
|
|
40
|
+
test with a human reviewing framework and code before it lands, use
|
|
41
|
+
`agents/canary-test-author.md` (wired to `/canary-write-test`). For a
|
|
42
|
+
single-file, automatic write-run-revise loop via MCP tools, use
|
|
43
|
+
`agents/canary-test-generator.md`.
|
|
44
|
+
|
|
45
|
+
## Process
|
|
46
|
+
|
|
47
|
+
### Phase 1: CLARIFY — Resolve the Requirement
|
|
48
|
+
|
|
49
|
+
1. **Confirm test type if ambiguous.** If the prompt doesn't clearly indicate
|
|
50
|
+
`unit | api | e2e | performance`, ask one targeted question before invoking
|
|
51
|
+
the pipeline. The classifier will guess, but a wrong guess costs a
|
|
52
|
+
regeneration.
|
|
53
|
+
2. **Confirm target framework if the user has a preference.** The recommender
|
|
54
|
+
will pick by category from the registry; if the user explicitly wants
|
|
55
|
+
Playwright but the registry would pick Cypress for the category, surface that
|
|
56
|
+
mismatch now.
|
|
57
|
+
3. **Capture concrete inputs.** Endpoint URL, payload shape, expected status,
|
|
58
|
+
selectors, performance thresholds — whatever the test actually needs. Vague
|
|
59
|
+
prompts produce vague tests.
|
|
60
|
+
|
|
61
|
+
### Phase 2: GENERATE — Run the Pipeline
|
|
62
|
+
|
|
63
|
+
1. **Invoke generation** by running the `/canary-write-test` slash command in
|
|
64
|
+
Claude Code with the requirement as its prompt.
|
|
65
|
+
|
|
66
|
+
2. **Read the printed classification + recommendation.** Verify the resolved
|
|
67
|
+
`test_type` and `framework` match intent. If they don't, refine the prompt
|
|
68
|
+
and re-run — do not hand-edit the generated file to compensate for a
|
|
69
|
+
misclassification.
|
|
70
|
+
3. **Locate the output.** The CLI prints an absolute path under
|
|
71
|
+
`tests/generated/<category>/`. The orchestrator return dict's `output_path`
|
|
72
|
+
is authoritative.
|
|
73
|
+
|
|
74
|
+
### Phase 3: VALIDATE — Execute or Dry-Run
|
|
75
|
+
|
|
76
|
+
1. **Run the generated test** with the framework's CLI (or pass `--execute` to
|
|
77
|
+
the generator). For api/e2e tests, run against a known-good environment
|
|
78
|
+
first.
|
|
79
|
+
2. **If execution fails, classify the failure:**
|
|
80
|
+
- **Generation error** (syntax, wrong API shape) → regenerate with a more
|
|
81
|
+
specific prompt; don't hand-fix unless trivial
|
|
82
|
+
- **Environment error** (missing creds, wrong base URL) → fix the env, rerun
|
|
83
|
+
- **Real assertion failure** (the SUT behaves differently than the prompt
|
|
84
|
+
asserted) → this is a useful signal; review with the requester before
|
|
85
|
+
changing the test
|
|
86
|
+
3. **Log the run.** Append the requirement, classification, framework, and
|
|
87
|
+
pass/fail to `docs/CANARY_STATE.md` so downstream sessions can pick up
|
|
88
|
+
context.
|
|
89
|
+
|
|
90
|
+
### Phase 4: PROMOTE — Move from Generated to Committed
|
|
91
|
+
|
|
92
|
+
If the generated test passes review and belongs in the committed suite, use the
|
|
93
|
+
[`canary-promote-test`](../canary-promote-test/SKILL.md) skill. Promotion is its
|
|
94
|
+
own workflow — don't collapse it into this one.
|
|
95
|
+
|
|
96
|
+
## Canary Integration
|
|
97
|
+
|
|
98
|
+
- **`/canary-write-test "<prompt>"`** — Primary entry. Runs the full classify →
|
|
99
|
+
recommend → generate pipeline in the Claude Code session and can immediately
|
|
100
|
+
run the generated test.
|
|
101
|
+
- **`CanaryOrchestrator.run(prompt, execute=False)`** — Programmatic entry.
|
|
102
|
+
Returns the structured pipeline-result dict.
|
|
103
|
+
- **`ts/src/data/frameworks/registry.json`** — Maps `test_type` → framework.
|
|
104
|
+
Edit here when adding a new framework; never hard-code framework choices in
|
|
105
|
+
callers.
|
|
106
|
+
- **Generation runs in your Claude Code session** — there is no LLM provider
|
|
107
|
+
layer or API key to configure (that layer was removed in v3.0).
|
|
108
|
+
|
|
109
|
+
## Success Criteria
|
|
110
|
+
|
|
111
|
+
- The generated file parses and runs under its framework's CLI
|
|
112
|
+
- The classifier's `test_type` matches the user's actual intent
|
|
113
|
+
- The recommender's framework choice resolves from the registry (no nulls)
|
|
114
|
+
- The execution result is captured in the return dict (when `execute=True`)
|
|
115
|
+
- A promoted test passes review and runs cleanly in CI
|
|
116
|
+
|
|
117
|
+
## Rationalizations to Reject
|
|
118
|
+
|
|
119
|
+
| Rationalization | Why It Is Wrong |
|
|
120
|
+
| ----------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
121
|
+
| "The classifier picked the wrong type but I'll hand-fix the output" | The hand-fix masks a real classifier gap. Refine the prompt or file a classifier issue — don't paper over routing bugs in the generated file. |
|
|
122
|
+
| "I'll commit the generated test as-is, it's good enough" | Generated tests live in `tests/generated/` for a reason — they're unreviewed scratch. Promote intentionally. |
|
|
123
|
+
| "The registry doesn't have an entry for this test_type, I'll add `framework: null`" | Null breaks the contract. Every `test_type` must map to a framework. Add a real entry or change the classifier output. |
|
|
124
|
+
| "I'll skip the validation phase, the test looks right" | LLM output that looks right and runs are different things. Always execute (or dry-run) before promoting. |
|
|
125
|
+
|
|
126
|
+
## Examples
|
|
127
|
+
|
|
128
|
+
### Example: API test for a known endpoint
|
|
129
|
+
|
|
130
|
+
**Prompt:** `Test that POST /v1/orders returns 201 with a valid payload`
|
|
131
|
+
|
|
132
|
+
**Pipeline trace:**
|
|
133
|
+
|
|
134
|
+
```text
|
|
135
|
+
Classification: intent=generate_tests, test_type=api, confidence=0.85
|
|
136
|
+
Recommendation: framework=requests-pytest, ext=.py, category=api
|
|
137
|
+
Output: tests/generated/api/orders_post_201.py
|
|
138
|
+
Execution: returncode=0, 1 passed in 0.42s
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
**Action:** Review the generated file, promote to
|
|
142
|
+
`tests/api/orders_post_201.py`, drop the timestamped header.
|
|
143
|
+
|
|
144
|
+
### Example: Ambiguous prompt — clarification first
|
|
145
|
+
|
|
146
|
+
**Prompt:** `Test the new orders feature`
|
|
147
|
+
|
|
148
|
+
**Action:** Do NOT invoke the pipeline yet. Ask: "Is this an end-to-end UI test
|
|
149
|
+
of the checkout flow, an API contract test for `/v1/orders`, or a unit test of
|
|
150
|
+
the order-validation function?" Only after the user picks should you run
|
|
151
|
+
`generate`.
|
|
152
|
+
|
|
153
|
+
### Example: Performance test with thresholds
|
|
154
|
+
|
|
155
|
+
**Prompt:** `Load test /v1/search at 200 RPS for 5 minutes, p95 latency < 300ms`
|
|
156
|
+
|
|
157
|
+
**Pipeline trace:**
|
|
158
|
+
|
|
159
|
+
```text
|
|
160
|
+
Classification: test_type=performance, confidence=0.95
|
|
161
|
+
Recommendation: framework=k6, ext=.js, category=performance
|
|
162
|
+
Output: tests/generated/performance/search_load.js
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
**Validation:** Run against a staging environment, not prod. Compare p95 to the
|
|
166
|
+
threshold; if the test passes locally but the threshold was unrealistic, surface
|
|
167
|
+
that to the requester before promoting.
|
|
168
|
+
|
|
169
|
+
## Escalation
|
|
170
|
+
|
|
171
|
+
- **When the registry has no entry for the classified `test_type`:** Stop and
|
|
172
|
+
file a registry update. Do not invent a framework name.
|
|
173
|
+
- **When the generated test repeatedly fails to parse:** This usually means the
|
|
174
|
+
prompt is under-specified. Narrow it to a single behavior and regenerate
|
|
175
|
+
before assuming a bug.
|
|
176
|
+
- **When execution requires creds you don't have:** Surface the missing-cred
|
|
177
|
+
error to the user; never embed dummy creds in a generated test to make it
|
|
178
|
+
"run".
|
|
179
|
+
- **When the classifier's confidence is below 0.7:** Treat as a clarification
|
|
180
|
+
trigger, not a generation trigger. Loop back to Phase 1. Note: `confidence` is
|
|
181
|
+
a hand-calibrated heuristic prior per keyword-match branch in
|
|
182
|
+
`ts/src/core/classifier.ts`, not a statistically calibrated probability — 0.7
|
|
183
|
+
is a coarse "does this branch's signal look weak" cutoff, not P(correct
|
|
184
|
+
classification) ≥ 0.7. Don't read finer distinctions (e.g. 0.85 vs. 0.95) as
|
|
185
|
+
meaningfully different confidence levels.
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: canary-instrument
|
|
3
|
+
description:
|
|
4
|
+
Instrument a Playwright run with OpenTelemetry and emit a run.json artifact
|
|
5
|
+
correlating every test to the outbound HTTP requests it made — "which test
|
|
6
|
+
made which request?" — with zero manual bookkeeping in test code. Trace-only
|
|
7
|
+
v1 contract, additive-safe for future pytest/k6/node producers. Self-contained
|
|
8
|
+
(bundles its own run-type factories and span reader).
|
|
9
|
+
cli: scripts/cli.mjs
|
|
10
|
+
requires: [node>=20]
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
# Canary Instrument
|
|
14
|
+
|
|
15
|
+
Correlate every outbound HTTP request in a Playwright run to the test that made
|
|
16
|
+
it, using OTel span parent/child relationships. Zero required external
|
|
17
|
+
dependencies to produce output — default file-based span export, no OTel
|
|
18
|
+
collector needed.
|
|
19
|
+
|
|
20
|
+
## Setup (two manual steps, once per suite)
|
|
21
|
+
|
|
22
|
+
This skill ships fixture _files_ you wire into your own suite — it does not
|
|
23
|
+
vendor OTel as a dependency of the `canary` package itself. Install these first:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
npm install --save-dev \
|
|
27
|
+
@opentelemetry/sdk-node @opentelemetry/api \
|
|
28
|
+
@opentelemetry/auto-instrumentations-node \
|
|
29
|
+
@opentelemetry/exporter-trace-otlp-http
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
**1. Bootstrap the OTel SDK before Playwright starts** — add `NODE_OPTIONS` to
|
|
33
|
+
your test command (or `playwright.config.ts`'s `webServer`/CI step):
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
NODE_OPTIONS="--import ./node_modules/canary/agents/skills/claude-code/canary-instrument/scripts/otel_bootstrap/instrument.mjs" \
|
|
37
|
+
npx playwright test
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Copy `otel_bootstrap/instrument.mjs` into your repo (e.g. `otel/instrument.mjs`)
|
|
41
|
+
if you'd rather not reference the path inside `node_modules`.
|
|
42
|
+
|
|
43
|
+
**2. Merge the root-span fixture into your `fixtures.ts`:**
|
|
44
|
+
|
|
45
|
+
```ts
|
|
46
|
+
import { test as base } from '@playwright/test';
|
|
47
|
+
import { withTestSpan } from './otel_bootstrap/playwright-fixture';
|
|
48
|
+
|
|
49
|
+
export const test = withTestSpan(base);
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Every test using this `test` export now opens a root span carrying
|
|
53
|
+
`test.id`/`test.title`/`test.file`, and every HTTP call the test makes nests
|
|
54
|
+
under it automatically — no manual span code in individual tests.
|
|
55
|
+
|
|
56
|
+
## Invocation
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
canary skills run canary-instrument -- \
|
|
60
|
+
--spans test-results/trace --output test-results \
|
|
61
|
+
[--suite-type e2e_ui]
|
|
62
|
+
|
|
63
|
+
# Usage and options (exits 0; --spans/--output are not required for --help):
|
|
64
|
+
canary skills run canary-instrument -- --help
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
An unknown flag, a missing required flag, and a value-flag left without its
|
|
68
|
+
value are all usage errors (exit 2). `--help` short-circuits ahead of the
|
|
69
|
+
required-argument check, so it never complains about `--spans`/`--output`.
|
|
70
|
+
|
|
71
|
+
Writes `test-results/run.json`. Creates `--output` if it doesn't exist.
|
|
72
|
+
Missing/empty `--spans` produces `trace: {spans_total: 0, by_test: []}`, not a
|
|
73
|
+
failure. `--suite-type` is a free-form string (no enum) — pass whatever label
|
|
74
|
+
describes your suite.
|
|
75
|
+
|
|
76
|
+
## `run.json` v1 contract (trace-only)
|
|
77
|
+
|
|
78
|
+
```jsonc
|
|
79
|
+
{
|
|
80
|
+
"schema_version": 1,
|
|
81
|
+
"suite_type": "",
|
|
82
|
+
"generated_at": "2026-07-15T18:00:00+00:00",
|
|
83
|
+
"trace": {
|
|
84
|
+
"spans_total": 124,
|
|
85
|
+
"by_test": [
|
|
86
|
+
{
|
|
87
|
+
"test_id": "users-spec:1", // "__setup__" for orphan traffic
|
|
88
|
+
"test_title": "lists users",
|
|
89
|
+
"test_file": "tests/users.spec.ts",
|
|
90
|
+
"trace_id": "abc123...",
|
|
91
|
+
"outcome": "passed",
|
|
92
|
+
"requests": [
|
|
93
|
+
{
|
|
94
|
+
"method": "GET",
|
|
95
|
+
"url": "http://localhost:3000/users/1",
|
|
96
|
+
"route": "/users/:id",
|
|
97
|
+
"status": 200,
|
|
98
|
+
"duration_ms": 12.4,
|
|
99
|
+
"span_id": "def456...",
|
|
100
|
+
"started_at": "2026-07-15T18:00:01+00:00",
|
|
101
|
+
},
|
|
102
|
+
],
|
|
103
|
+
},
|
|
104
|
+
],
|
|
105
|
+
},
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
No `coverage` key, no `canary_run_id` key — cut for v1 (see
|
|
110
|
+
`docs/knowledge/decisions/0006-otel-test-side-tracing.md` and
|
|
111
|
+
`docs/changes/canary-instrument/proposal.md`). Additive-only evolution: new
|
|
112
|
+
optional fields may appear later; existing fields never change meaning.
|
|
113
|
+
|
|
114
|
+
## Sending spans to a collector (optional)
|
|
115
|
+
|
|
116
|
+
Set `OTEL_EXPORTER_OTLP_ENDPOINT` before the test run and spans are
|
|
117
|
+
_additionally_ streamed there — the file exporter still writes
|
|
118
|
+
`test-results/trace/otel-spans.*.jsonl` either way, so `canary-instrument`'s own
|
|
119
|
+
correlation is never dependent on a collector being up. If your org's endpoint
|
|
120
|
+
is recorded in company-knowledge, export it first:
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
export OTEL_EXPORTER_OTLP_ENDPOINT="$(canary company-knowledge show --json | jq -r '.otel_exporter_endpoint')"
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
See `docs/guides/company-knowledge.md` for the `otel_exporter_endpoint` field.
|
|
127
|
+
|
|
128
|
+
## CI wiring (GitHub Actions)
|
|
129
|
+
|
|
130
|
+
```yaml
|
|
131
|
+
- name: Run Playwright (instrumented)
|
|
132
|
+
env:
|
|
133
|
+
NODE_OPTIONS: '--import ./otel/instrument.mjs'
|
|
134
|
+
run:
|
|
135
|
+
npx playwright test --reporter=json --output-file=test-results/results.json
|
|
136
|
+
|
|
137
|
+
- name: Correlate tests to HTTP spans
|
|
138
|
+
if: always()
|
|
139
|
+
run: |
|
|
140
|
+
canary skills run canary-instrument -- \
|
|
141
|
+
--spans test-results/trace --output test-results
|
|
142
|
+
|
|
143
|
+
- name: Upload run.json
|
|
144
|
+
if: always()
|
|
145
|
+
uses: actions/upload-artifact@v4
|
|
146
|
+
with:
|
|
147
|
+
name: run-trace
|
|
148
|
+
path: test-results/run.json
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
## Related skills
|
|
152
|
+
|
|
153
|
+
- `canary-test-reporter` — Markdown/JSON test summary; `run.json`'s `by_test[]`
|
|
154
|
+
rows are structurally similar to its `TestResult` shape (title/status as join
|
|
155
|
+
keys) — a future consumer can read both artifacts with one join key.
|
|
156
|
+
- `canary-fail-fast` — aborts a broken run early; use alongside this skill for
|
|
157
|
+
complete CI coverage.
|