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.
Files changed (83) hide show
  1. package/agents/skills/README.md +308 -0
  2. package/agents/skills/canary:generate.md +49 -0
  3. package/agents/skills/canary:init.md +37 -0
  4. package/agents/skills/canary:migrate.md +66 -0
  5. package/agents/skills/claude-code/canary-add-framework/SKILL.md +248 -0
  6. package/agents/skills/claude-code/canary-blackhawk/SKILL.md +170 -0
  7. package/agents/skills/claude-code/canary-blackhawk/scripts/cli.mjs +188 -0
  8. package/agents/skills/claude-code/canary-blackhawk/scripts/rules.mjs +120 -0
  9. package/agents/skills/claude-code/canary-blackhawk/scripts/scanner.mjs +244 -0
  10. package/agents/skills/claude-code/canary-blackhawk/scripts/string-literals.mjs +116 -0
  11. package/agents/skills/claude-code/canary-cassandra/SKILL.md +180 -0
  12. package/agents/skills/claude-code/canary-cassandra/scripts/cli.mjs +268 -0
  13. package/agents/skills/claude-code/canary-cassandra/scripts/engine.mjs +95 -0
  14. package/agents/skills/claude-code/canary-ci-ready/SKILL.md +161 -0
  15. package/agents/skills/claude-code/canary-ci-ready/skill.yaml +14 -0
  16. package/agents/skills/claude-code/canary-company-knowledge/SKILL.md +196 -0
  17. package/agents/skills/claude-code/canary-critical-areas/SKILL.md +142 -0
  18. package/agents/skills/claude-code/canary-critical-areas/skill.yaml +16 -0
  19. package/agents/skills/claude-code/canary-edge-case-discovery/SKILL.md +160 -0
  20. package/agents/skills/claude-code/canary-edge-case-discovery/skill.yaml +16 -0
  21. package/agents/skills/claude-code/canary-fail-fast/SKILL.md +75 -0
  22. package/agents/skills/claude-code/canary-fail-fast/scripts/cli.mjs +118 -0
  23. package/agents/skills/claude-code/canary-fail-fast/scripts/digest.mjs +69 -0
  24. package/agents/skills/claude-code/canary-fail-fast/scripts/failures.mjs +60 -0
  25. package/agents/skills/claude-code/canary-fail-fast/scripts/fastfail_check.mjs +43 -0
  26. package/agents/skills/claude-code/canary-fail-fast/scripts/parse.mjs +149 -0
  27. package/agents/skills/claude-code/canary-failure-impact/SKILL.md +153 -0
  28. package/agents/skills/claude-code/canary-failure-impact/skill.yaml +15 -0
  29. package/agents/skills/claude-code/canary-fleet-health/SKILL.md +196 -0
  30. package/agents/skills/claude-code/canary-generate-test/SKILL.md +185 -0
  31. package/agents/skills/claude-code/canary-instrument/SKILL.md +157 -0
  32. package/agents/skills/claude-code/canary-instrument/scripts/cli.mjs +178 -0
  33. package/agents/skills/claude-code/canary-instrument/scripts/otel_bootstrap/instrument.mjs +96 -0
  34. package/agents/skills/claude-code/canary-instrument/scripts/otel_bootstrap/playwright-fixture.ts +44 -0
  35. package/agents/skills/claude-code/canary-instrument/scripts/run_types.mjs +81 -0
  36. package/agents/skills/claude-code/canary-instrument/scripts/span_reader.mjs +187 -0
  37. package/agents/skills/claude-code/canary-katana/SKILL.md +243 -0
  38. package/agents/skills/claude-code/canary-katana/scripts/alarm.mjs +296 -0
  39. package/agents/skills/claude-code/canary-katana/scripts/cli.mjs +247 -0
  40. package/agents/skills/claude-code/canary-katana/scripts/diffscan.mjs +0 -0
  41. package/agents/skills/claude-code/canary-katana/scripts/ledger.mjs +183 -0
  42. package/agents/skills/claude-code/canary-pr-guardian/SKILL.md +129 -0
  43. package/agents/skills/claude-code/canary-pr-guardian/skill.yaml +17 -0
  44. package/agents/skills/claude-code/canary-promote-test/SKILL.md +228 -0
  45. package/agents/skills/claude-code/canary-savant/SKILL.md +233 -0
  46. package/agents/skills/claude-code/canary-savant/scripts/cli.mjs +274 -0
  47. package/agents/skills/claude-code/canary-savant/scripts/restoration.mjs +274 -0
  48. package/agents/skills/claude-code/canary-savant/scripts/rules.mjs +168 -0
  49. package/agents/skills/claude-code/canary-savant/scripts/runner.mjs +572 -0
  50. package/agents/skills/claude-code/canary-savant/scripts/scanner.mjs +374 -0
  51. package/agents/skills/claude-code/canary-savant/scripts/string-literals.mjs +116 -0
  52. package/agents/skills/claude-code/canary-setup-harness/SKILL.md +263 -0
  53. package/agents/skills/claude-code/canary-shadow/SKILL.md +131 -0
  54. package/agents/skills/claude-code/canary-shadow/scripts/cases.example.json +32 -0
  55. package/agents/skills/claude-code/canary-shadow/scripts/cli.mjs +195 -0
  56. package/agents/skills/claude-code/canary-ship/SKILL.md +177 -0
  57. package/agents/skills/claude-code/canary-ship/skill.yaml +16 -0
  58. package/agents/skills/claude-code/canary-strix/SKILL.md +130 -0
  59. package/agents/skills/claude-code/canary-strix/scripts/cli.mjs +255 -0
  60. package/agents/skills/claude-code/canary-strix/scripts/scanner.mjs +252 -0
  61. package/agents/skills/claude-code/canary-strix/scripts/terms.mjs +132 -0
  62. package/agents/skills/claude-code/canary-test-pipeline/SKILL.md +159 -0
  63. package/agents/skills/claude-code/canary-test-pipeline/skill.yaml +19 -0
  64. package/agents/skills/claude-code/canary-test-reporter/SKILL.md +138 -0
  65. package/agents/skills/claude-code/canary-test-reporter/scripts/cli.mjs +98 -0
  66. package/agents/skills/claude-code/canary-test-reporter/scripts/json_report.mjs +58 -0
  67. package/agents/skills/claude-code/canary-test-reporter/scripts/parse.mjs +216 -0
  68. package/agents/skills/claude-code/canary-test-reporter/scripts/render.mjs +114 -0
  69. package/agents/skills/lib/parse-args.mjs +214 -0
  70. package/dist/engine/cli-commands.js +3 -65
  71. package/dist/engine/core/quality-scorer.js +15 -2
  72. package/dist/engine/core/skill-dispatch.js +115 -0
  73. package/dist/engine/core/skill-examples.js +103 -3
  74. package/dist/engine/core/skill-registry.js +59 -4
  75. package/dist/engine/core/test-files.js +77 -0
  76. package/dist/engine/core/vacuity-scanner.js +184 -14
  77. package/dist/engine/guardian/analysis-emit.js +7 -2
  78. package/dist/engine/guardian/cli.js +121 -9
  79. package/dist/engine/guardian/pr-check.js +317 -18
  80. package/dist/engine/skills-cli.js +123 -19
  81. package/dist/reporters/testtracker.d.ts +1 -1
  82. package/dist/reporters/testtracker.js +1 -1
  83. 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.