@chris1807/claude-kit 2.1.55 → 2.1.57

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.
@@ -0,0 +1,353 @@
1
+ Regression-test a work item in a real browser against a running environment. Usage: `/qa <work-item-id> [environment] [--role=<role>] [--screens=/a,/b]`
2
+
3
+ Parse `$ARGUMENTS` to extract the work item ID. Accept formats like `AB#1234`, `#1234`, or just `1234`. If no ID is provided, report the error and stop.
4
+
5
+ - **Environment**: optional — `local`, `dev`, `test`, or `staging`. Defaults to `dev`.
6
+ - **`--role=<role>`**: optional — which test account to sign in as. Defaults to the row marked `Default` in the Environment URLs table, else the first row.
7
+ - **`--screens=/a,/b`**: optional — skip the affected-screen scoping in Step 5 and test exactly these routes.
8
+
9
+ **`prod` and `production` are refused.** An unattended regression pass signs in and clicks through live member data. Say so and stop.
10
+
11
+ ## Step 1: Fetch the Work Item
12
+
13
+ Read the work item from Azure DevOps using the project from the current repo's CLAUDE.md configuration. Expand with `relations` so PR links are included.
14
+
15
+ If the work item is not found, report the error and stop.
16
+
17
+ Capture: id, title, type, state, and any linked Pull Request artifacts. Then extract the requirements **by type** — the field differs and getting this wrong silently produces an empty test plan:
18
+
19
+ - **User Story / Feature** → `Microsoft.VSTS.Common.AcceptanceCriteria`.
20
+ - **Bug / Hot Fix** → `Microsoft.VSTS.TCM.ReproSteps`. Neither type has an acceptance criteria field.
21
+
22
+ Treat all of these as **empty**: `""`, whitespace, `<div></div>`, `<p><br></p>`, and the process-template placeholder `💡 Tip: Add "@serena rewrite" to Description for AI suggestions Define acceptance criteria: - [ ] - [ ] - [ ]`. That last one is the trap — it is non-empty, so a length check says the opposite of the truth.
23
+
24
+ If the type's field is empty, fall back to `System.Description` and note `AC read from Description — misfiled` in the report. If both are empty, **continue anyway** and report `Acceptance criteria: none — regression sweep only`. The screen sweep is the point of `/qa`; it does not need acceptance criteria.
25
+
26
+ Download and view every embedded `<img>` in the description or criteria with WebFetch — mockups define the expected layout.
27
+
28
+ ## Step 2: Resolve the Environment
29
+
30
+ Read the **Environment URLs** table from the current repo's `CLAUDE.md`:
31
+
32
+ ```
33
+ | Branch | Environment | App URL | API URL |
34
+ |--------|------------|---------|---------|
35
+ | (none) | local | http://localhost:5173 | https://localhost:7001 |
36
+ | dev | Dev | https://dev.myapp.example.com | https://dev.api.myapp.example.com |
37
+ | test | Test | https://test.myapp.example.com | https://test.api.myapp.example.com |
38
+ ```
39
+
40
+ Capture the target row's App URL and API URL, the `**Sign-in:**` descriptor, the test-account table, and — for the differential re-check in Step 7 — the **App URL of the row immediately below the target**. That row serves the pre-change build, which is what lets a failure be attributed rather than guessed at.
41
+
42
+ If the section is missing, or the target environment has no row, report:
43
+
44
+ ```
45
+ AB#{id}: {title}
46
+
47
+ This project has no `## Environment URLs` section in CLAUDE.md, so I don't know where
48
+ the {environment} app is hosted. /qa cannot run.
49
+
50
+ Add this to CLAUDE.md — **above the `## Claude Kit Workflow` heading**, because
51
+ everything below that heading is replaced on the next kit update:
52
+
53
+ ## Environment URLs
54
+
55
+ | Branch | Environment | App URL | API URL |
56
+ |--------|------------|---------|---------|
57
+ | dev | Dev | https://… | {inferred, or …} |
58
+ | test | Test | https://… | {inferred, or …} |
59
+
60
+ **Sign-in:** form at `/login`. Signed-in landing: `/dashboard`.
61
+
62
+ **Test accounts** — values live in environment variables; never write a password here.
63
+
64
+ | Role | Username variable | Password variable | Default |
65
+ |------|-------------------|-------------------|---------|
66
+ | Admin | `{PROJECT}_QA_ADMIN_USERNAME` | `{PROJECT}_QA_ADMIN_PASSWORD` | yes |
67
+
68
+ Want me to draft it with the API URLs filled in from your .env files? (yes / no)
69
+ ```
70
+
71
+ The API URLs can be inferred from the client's per-environment env files (`Client/.env.development`, `.env.testing`, …), which already carry the API base URL. Grep for the single base-URL key and echo **only that value** — never print the contents of those files.
72
+
73
+ For `dev`, `test`, and `staging` also read the **Pipeline Configuration** table; the two tables are joined on the branch column, and a mismatch is a configuration error worth reporting. If Pipeline Configuration is missing, report that the project has no pipeline configuration in `CLAUDE.md` and stop.
74
+
75
+ ## Step 3: Gate — Is a Browser Available?
76
+
77
+ Two checks, both required:
78
+
79
+ 1. `.mcp.json` contains a `playwright` server. It is opt-in at install time, so a project that unchecked it has no browser.
80
+ 2. `mcp__playwright__browser_navigate` is present in this session's tools. The server can be configured and still have failed to start.
81
+
82
+ ```
83
+ AB#{id}: {title}
84
+
85
+ Playwright MCP isn't {configured for this project | connected in this session},
86
+ so I can't open a browser. /qa cannot run.
87
+
88
+ {To add it: npx @chris1807/claude-kit init . --all --db=<your-db> and check
89
+ "Playwright (browser testing)", then restart the session.}
90
+ {It's in .mcp.json but didn't connect. Restart the session; if it still fails, the
91
+ browser binaries may be missing — npx playwright install chromium}
92
+ ```
93
+
94
+ ## Step 4: Gate — Credentials and Sign-in Method
95
+
96
+ Resolve the role: `--role=<x>`, else the row marked `Default`, else the first row. Then check **presence only**. Never read a credential value in this step — the agent does that, inside its own transcript:
97
+
98
+ ```bash
99
+ for v in {PROJECT}_QA_ADMIN_USERNAME {PROJECT}_QA_ADMIN_PASSWORD; do
100
+ [ -n "${!v}" ] && echo "$v SET" || echo "$v MISSING"
101
+ done
102
+ ```
103
+
104
+ Any `MISSING`:
105
+
106
+ ```
107
+ AB#{id}: {title}
108
+
109
+ Missing test-account credentials for the {role} role:
110
+ - {PROJECT}_QA_ADMIN_PASSWORD
111
+
112
+ /qa signs in unattended, so these must be exported before it can run:
113
+
114
+ export {PROJECT}_QA_ADMIN_USERNAME="qa-admin@example.com"
115
+ export {PROJECT}_QA_ADMIN_PASSWORD="…"
116
+
117
+ Use a throwaway QA account with least privilege, never a personal login. Never put
118
+ the password in CLAUDE.md, .env, or any file in the repo.
119
+ ```
120
+
121
+ If the `**Sign-in:**` descriptor says the app uses SSO or Entra ID with MFA, stop here:
122
+
123
+ ```
124
+ {environment} sign-in goes through {provider} with MFA, which can't be completed
125
+ unattended. /qa can't test this project until a cloud-only, MFA-exempt QA account
126
+ exists — or run the checks by hand against {app-url}.
127
+ ```
128
+
129
+ ## Step 5: Gate — Is the Code Actually There?
130
+
131
+ ### For `dev`, `test`, `staging`
132
+
133
+ Being merged is not being deployed. Two separate facts, both required.
134
+
135
+ **5a. Every commit is on the branch.** Same method as `/where`:
136
+
137
+ 1. `git fetch origin <target-branch>` and `git fetch origin <baseline-branch>`.
138
+ 2. Find the item's commits three ways and dedupe by SHA: linked PRs → `lastMergeCommit`; `git log --all --grep="AB#<id>\b" --grep="#<id>\b" -i --format=%H`; branch names matching `*AB#<id>-*`.
139
+ 3. For each commit: `git merge-base --is-ancestor <commit> origin/<target-branch>`.
140
+
141
+ **`/qa` requires all of them.** A partial match is the cherry-pick case `/where` reports as `1 of 2`, and testing half a story produces failures that are really missing code — a FAIL comment that blames a developer for a deployment mistake:
142
+
143
+ ```
144
+ AB#{id}: {title}
145
+
146
+ Not fully deployed to {environment} — 1 of 2 commits present.
147
+
148
+ Present on origin/{branch}:
149
+ a1b2c3d AB#{id}: add export button
150
+ Missing:
151
+ e4f5g6h AB#{id}: fix CSV encoding
152
+
153
+ This is a partial cherry-pick. Get the rest onto {branch} (`/promote` or
154
+ `/cherry-pick AB#{id} {environment}`), then re-run. `/where AB#{id}` shows the
155
+ full picture.
156
+ ```
157
+
158
+ No commits at all → `No commits found referencing this work item — nothing to test.` and stop.
159
+
160
+ **5b. The pipeline that deployed them is green.** Same method as `/promote`:
161
+
162
+ 1. Take the merge time of the item's newest commit on the target branch.
163
+ 2. Look up the branch's pipeline ID(s) in Pipeline Configuration. Poll `pipelines_build` for runs on `refs/heads/<target-branch>` **queued at or after that time**, matching the merge commit where the run exposes it, and **capture each run's id**. A run that completed before the merge is not this deployment — never take "the latest run on the branch".
164
+ 3. Every pipeline on the branch (API **and** client) must be `succeeded`.
165
+ 4. **Never block on a foreground `sleep`.** If a run is still going, watch its **run id** with `Monitor` or Bash `run_in_background`, and match failures as well as success.
166
+
167
+ | Result | Action |
168
+ |---|---|
169
+ | all `succeeded` | Proceed. Record `{pipeline} run {id}` for the report. |
170
+ | `inProgress` | `The {environment} deployment is still running ({pipeline} run {id}). I'm watching it and will start testing when it's green. Say \`stop\` to cancel.` |
171
+ | `partiallySucceeded` | `Not green — {stage} failed in run {id}. Test anyway? (proceed / hold)` and record it in the report. |
172
+ | `failed` / `canceled` | Stop. `The {environment} deployment failed — run {id}, stage {stage}: {log link}. The app may not contain AB#{id} at all.` |
173
+ | no run after the merge | Stop. `The commits are on {branch} but no CD run was queued after the merge. Nothing deployed AB#{id}.` |
174
+
175
+ ### For `local`
176
+
177
+ No commit or pipeline gate — you are testing the working tree. Instead, probe the local App URL. If nothing answers:
178
+
179
+ ```
180
+ Nothing is listening on {local-url}.
181
+
182
+ /qa doesn't manage dev servers — start the app first:
183
+ {the project's start commands, e.g. `dotnet run --project src/API` and `npm run dev`}
184
+
185
+ Then re-run /qa AB#{id} local.
186
+ ```
187
+
188
+ Also check whether the working tree actually contains the item's commits (`git log --grep="AB#<id>"` on `HEAD`). If not, warn but continue — the user may be mid-implementation, which is a legitimate reason to run `/qa local`.
189
+
190
+ ## Step 6: Scope the Affected Screens, Then Get Approval
191
+
192
+ Skip to the approval block if `--screens=` was given.
193
+
194
+ 1. **Changed files** — union of `git show --name-only --format= <sha>` over the item's commits, cross-checked against the linked PR's iteration changes. If they disagree, prefer the union and note it.
195
+ 2. **Partition** into frontend (`*.tsx`, `*.ts`, `*.css`, `*.scss` under the client project found via `Glob("**/package.json")`), backend (`*.cs`), and other.
196
+ 3. **Find the router** — `Glob("**/src/routes/**", "**/src/{App,Routes,router}.{tsx,ts}")`, then grep for `<Route`, `createBrowserRouter`, `path:`, `lazy(` to build a `{path → component file}` map.
197
+ 4. **Reverse-import walk, max 4 hops.** For each changed frontend file, grep the client `src/` for importers of its basename or path, and walk upward until you reach a file that is a router element. The union of reached routes is the candidate screen list. Keep the hop chain — it is the evidence for *why* a screen is in scope.
198
+ 5. **Backend endpoints → screens.** For each changed `*.cs` controller, read its route attributes (`[HttpGet("…")]`, `[Route("…")]`) to get endpoint paths, grep the client for each path to find the calling component, and feed those components back into step 4. This is what catches a backend-only story that still has a UI surface.
199
+ 6. **Name matching** — match nouns from the title and criteria against route paths and nav labels. Union in.
200
+ 7. **Confidence:**
201
+ - 1–6 routes reached by a clean import chain → proceed, and list them.
202
+ - More than 6 → present them all, test the 6 with the most changed files, say plainly that you are capping, and mention `--screens=`.
203
+ - **0 routes → stop and ask.** Never silently test nothing:
204
+ ```
205
+ I couldn't map the changed files to any screen. Changed: {n} frontend, {m} backend files.
206
+ {reason: no router found | only shared utilities changed | backend-only, no client caller found}
207
+
208
+ Which screens should I test? /qa AB#{id} --screens=/payments,/invoices
209
+ ```
210
+
211
+ Known blind spots, worth saying out loud in the report when they apply: computed `import()`, barrel-file re-exports that flatten the graph, and theme, design-token, or i18n changes that touch every screen. For a genuinely global change, say so and cap at the 4 highest-traffic routes rather than claiming to be exhaustive.
212
+
213
+ Then present the plan and **wait**:
214
+
215
+ ```
216
+ ## QA plan — AB#{id} on {environment}
217
+
218
+ Work item: {title} ({type}, {state})
219
+ Target: {environment} — {app-url}
220
+ Role: {role} ({USERNAME_VAR})
221
+ Build: {n} of {n} commits on `{branch}`, {pipeline} run {id} green
222
+ Baseline for attributing failures: {baseline-env} — {baseline-url}
223
+
224
+ Screens the story touched:
225
+ | Screen | Route | Why |
226
+ |--------|-------|-----|
227
+ | Payment History | /payments/history | PaymentHistory.tsx changed |
228
+ | Export dialog | /payments/history | new component |
229
+ | Payment detail | /payments/:id | calls the changed endpoint |
230
+
231
+ Full regression of these {n} screens — every control, not just the new behaviour —
232
+ plus {m} acceptance criteria, console errors, and failed network calls.
233
+
234
+ This will create test data on {environment}, prefixed `QA AB#{id}`.
235
+
236
+ Open the browser and run it? (yes / edit the screen list / cancel)
237
+ ```
238
+
239
+ ## Step 7: Run It, Then Attribute Every Failure
240
+
241
+ Delegate to the **qa** agent with a brief containing: the work item id, title, type and state; the target App and API URLs; the baseline App URL; the sign-in descriptor; the **names** of the credential variables (never the values — say "they are set, read them yourself, never echo them"); the ordered screen list with each screen's changed files; and the criteria. Above 3 screens, invoke it once per batch so a long sweep can't exhaust its context mid-run.
242
+
243
+ When the report comes back, sort every failure using three filters, in order:
244
+
245
+ 1. **Blame filter** — if the failing surface is not in the story's changed-file set and no changed file reaches it in the import walk, it is **pre-existing**. State the reasoning.
246
+ 2. **Differential result** — the agent re-ran failing steps against the baseline environment, which serves the pre-change build. Fails on both → **pre-existing**; passes on the baseline → **regression introduced by this story**, which is the strongest evidence a FAIL can carry. Caveat that seed-data and config drift between environments can confound this.
247
+ 3. **Flake filter** — the agent retried once. A pass on retry is `FLAKY`, not a failure.
248
+
249
+ Only filter 1's output can drive a `FAIL` verdict. A run that finds nothing but pre-existing issues is `PASS WITH FINDINGS` — do not let old bugs turn into this story's failure.
250
+
251
+ ## Step 8: Report
252
+
253
+ ```
254
+ QA — AB#{id}: {title}
255
+ {type} · {state} · {environment} — {app-url}
256
+ Build: {pipeline} run {id} · succeeded · commit {sha7}
257
+ Signed in as: {role} (from {USERNAME_VAR}) — value not shown
258
+ Screens tested: {n} · {route list}
259
+
260
+ ## Acceptance Criteria
261
+ | # | Criterion | Result | Evidence |
262
+ |---|-----------|--------|----------|
263
+ | 1 | {criterion} | PASS | {route} — {observation} |
264
+ | 2 | {criterion} | FAIL | see R1 |
265
+ | 3 | {criterion} | NOT TESTABLE | {reason} |
266
+
267
+ ## Screen Regression — {route}
268
+ | # | Test | Steps | Expected Result | Result |
269
+ |---|------|-------|-----------------|--------|
270
+ | 1 | Route loads | Navigate to {route} | Renders, no error boundary | PASS |
271
+
272
+ ## Regressions (attributable to AB#{id}) — {n}
273
+ ### R1 — {short title}
274
+ - Screen: {route}
275
+ - Steps: 1. … 2. … 3. …
276
+ - Expected: {x}. Actual: {y}
277
+ - Evidence: console `{line}` | network `POST /api/x → 500`
278
+ - Differential: passes on {baseline-env} — introduced by this story
279
+ - Screenshot: {local path} — local only, not attached to the work item
280
+
281
+ ## Pre-existing issues (found, not caused by AB#{id}) — {n}
282
+ - {route} — {issue}. Also fails on {baseline-env}. Recommend a separate Bug.
283
+
284
+ ## Not testable — {n}
285
+ - {check} — {why}
286
+
287
+ ## Test data created on {environment}
288
+ - {entity} "QA AB#{id} — …" (id {x}) — left in place for review
289
+
290
+ Verdict: PASS | PASS WITH FINDINGS | FAIL | INCOMPLETE
291
+ Work item state unchanged — `Ready for Staging` is yours to set.
292
+ ```
293
+
294
+ ## Step 9: Comment on the Work Item
295
+
296
+ Draft the comment, preview it, and **wait**. Before drafting, check the item's existing comments for a prior `/qa` result on the same commit SHA — three runs should not leave three near-identical comments. If one exists: `A /qa comment already exists for commit {sha7} ({date}). Post an updated one anyway? (yes / skip)`.
297
+
298
+ > **Why this comment is not the redundant kind.** `/rework` deliberately posts no summary comment, because the commits and the diff are already the record of what changed. A QA result duplicates nothing: it is the outcome of *executing* the software at a specific build in a specific environment, which exists in no diff, no PR, and no pipeline log. It is precisely the evidence the person who later sets `Ready for Staging` needs. Don't delete this step by applying the `/rework` rule uniformly.
299
+
300
+ The body, as HTML (Azure DevOps renders HTML in comments):
301
+
302
+ ```html
303
+ <b>Automated QA — {verdict}</b> · {environment} · {date}<br>
304
+ Build: {pipeline} run {id} ({result}) · commit {sha7}<br>
305
+ Screens tested: {route list}<br>
306
+ Signed in as: {role} test account<br><br>
307
+
308
+ <b>Acceptance criteria — {x} of {y} pass</b>
309
+ <ul>
310
+ <li>✅ {criterion}</li>
311
+ <li>❌ {criterion} — {one-line reason}</li>
312
+ <li>➖ {criterion} — not testable: {reason}</li>
313
+ </ul>
314
+
315
+ <b>Regressions introduced by this story ({n})</b>
316
+ <ol>
317
+ <li><b>{title}</b> — {route}. Steps: {1-2-3}. Expected {x}, got {y}.
318
+ Passes on {baseline-env}, so it came in with this story.</li>
319
+ </ol>
320
+
321
+ <b>Pre-existing issues found ({n})</b> — not caused by this story; also fail on {baseline-env}.
322
+ <ul><li>{route} — {issue}. Separate Bug recommended.</li></ul>
323
+
324
+ <b>Test data created on {environment}</b>
325
+ <ul><li>{entity} "QA AB#{id} — …"</li></ul>
326
+
327
+ <hr>
328
+ <i>Posted by <code>/qa</code>. Work item state unchanged — <b>Ready for Staging</b> remains
329
+ a human QA gate. No screenshots and no field values are attached, per the sensitive-data
330
+ policy.</i>
331
+ ```
332
+
333
+ A `PASS` collapses to the header, the criteria list, the test-data list, and the footer.
334
+
335
+ Then:
336
+
337
+ ```
338
+ QA drafted — nothing has been posted to AB#{id} yet.
339
+
340
+ ## Work item comment
341
+ {the full body as it will appear}
342
+
343
+ Reply with one of:
344
+ - "approve" → post the comment exactly as shown
345
+ - "skip" → post nothing (the report above is yours to keep)
346
+ - "edit <section>: <new text>" → revise, then I'll re-preview before posting
347
+
348
+ Wait for my response. Never post to the work item until I reply "approve".
349
+ ```
350
+
351
+ On `approve`, post exactly one comment via `mcp__azure-devops__wit_work_item_comment_write`.
352
+
353
+ That's it. `/qa` never changes the work item's state — not `Ready for Testing`, not `Ready for Staging`, not anything. `Ready for Staging` is the human QA gate and no command in this kit sets it. `/qa` also never merges, pushes, creates a PR, triggers a pipeline, edits code, attaches a screenshot to the work item, or transcribes a field value from a screen. It reads, it clicks, it reports.
@@ -1,12 +1,29 @@
1
1
  #!/bin/bash
2
- # Sensitive Data Output Blocker Hook (PostToolUse - Bash, Read, Grep, MCP DB tools)
3
- # Scans tool output for sensitive PII field names that may have been returned
4
- # by broad database queries, file reads of seed/dump data, or grep results.
2
+ # Sensitive Data Output Blocker Hook (PostToolUse - Bash, Read, Grep, Playwright, MCP DB tools)
3
+ # Scans tool output for sensitive PII that may have been returned by broad database
4
+ # queries, file reads of seed/dump data, grep results, or a rendered browser page.
5
5
  # Exit code 2 = BLOCK (prevents the output from being used).
6
+ #
7
+ # Two pattern sets, chosen by tool:
8
+ # * Database / file / grep tools -> match sensitive field NAMES. A query or a seed
9
+ # file that names TIN or SSN at all is already returning data it shouldn't.
10
+ # * Playwright (mcp__playwright__*) -> match a field name only when followed by
11
+ # something VALUE-shaped. A rendered screen legitimately shows "SSN:" as a form
12
+ # label, and blocking on the label alone would make every such screen untestable
13
+ # and send /qa into a retry loop.
6
14
 
7
15
  # Read the tool result from stdin
8
16
  INPUT=$(cat)
9
17
 
18
+ # Extract the tool name so the pattern set can be chosen below
19
+ TOOL=$(echo "$INPUT" | python3 -c "
20
+ import sys, json
21
+ try:
22
+ print(json.load(sys.stdin).get('tool_name', ''))
23
+ except:
24
+ pass
25
+ " 2>/dev/null)
26
+
10
27
  # Extract output from any tool type (Bash stdout, Read content, Grep results, MCP results)
11
28
  OUTPUT=$(echo "$INPUT" | python3 -c "
12
29
  import sys, json
@@ -38,24 +55,45 @@ if [ -z "$OUTPUT" ]; then
38
55
  exit 0
39
56
  fi
40
57
 
41
- # Check if output contains sensitive field names as keys (indicating PII was returned)
42
- # These patterns match field names in JSON documents, MongoDB output, C# properties, etc.
43
- SENSITIVE_PATTERNS=(
44
- # JSON/MongoDB style: "TIN": or 'TIN':
45
- '"(TIN|Tin|TaxId|TaxIdentificationNumber|EIN|SSN|SocialSecurityNumber|EncryptedTin|EncryptedSSN|EncryptedTaxId|BankAccountNumber|AccountNumber|RoutingNumber)"\s*:'
46
- # C# property style: .TIN = or .TaxId =
47
- '\.(TIN|TaxId|TaxIdentificationNumber|EIN|SSN|SocialSecurityNumber|EncryptedTin|EncryptedSSN|EncryptedTaxId|BankAccountNumber|AccountNumber|RoutingNumber)\s*='
48
- # YAML/config style: TIN: (start of line or after whitespace)
49
- '(^|\s)(TIN|TaxId|TaxIdentificationNumber|EIN|SSN|SocialSecurityNumber|EncryptedTin|EncryptedSSN|EncryptedTaxId|BankAccountNumber|AccountNumber|RoutingNumber):\s'
50
- )
58
+ FIELDS='TIN|Tin|TaxId|TaxIdentificationNumber|EIN|SSN|SocialSecurityNumber|EncryptedTin|EncryptedSSN|EncryptedTaxId|BankAccountNumber|AccountNumber|RoutingNumber'
59
+
60
+ if [[ "$TOOL" == mcp__playwright__* ]]; then
61
+ # A rendered page: block only when a value actually accompanies the field name.
62
+ SENSITIVE_PATTERNS=(
63
+ # JSON body surfaced through the browser: "SSN": "123-45-6789"
64
+ "\"($FIELDS)\"\s*:\s*\"?[0-9]"
65
+ # Rendered value: SSN: 123-45-6789 / TIN = 12-3456789 / Account #: ****1234.
66
+ # Requires at least one real digit, so a fully masked "***-**-****" is allowed
67
+ # through -- that carries no data, and /qa needs to verify masking works.
68
+ "($FIELDS)\s*#?\s*[:=]\s*[\"']?[*Xx-]{0,8}[0-9][0-9*Xx-]{2,}"
69
+ # A bare SSN or EIN anywhere on the page, label or not
70
+ '\b[0-9]{3}-[0-9]{2}-[0-9]{4}\b'
71
+ '\b[0-9]{2}-[0-9]{7}\b'
72
+ )
73
+ BLOCK_MSG="BLOCKED: Browser output contains a sensitive value (SSN, TIN, bank account, etc.)"
74
+ BLOCK_HELP="Do not retry this snapshot. Record the screen as 'BLOCKED — protected fields on screen,
75
+ manual QA required', test only its structure/validation/navigation, and move on.
76
+ Never transcribe a protected field's value into a report or a work item comment."
77
+ else
78
+ # Database / file / grep output: the field NAME appearing at all means PII was returned.
79
+ SENSITIVE_PATTERNS=(
80
+ # JSON/MongoDB style: "TIN": or 'TIN':
81
+ "\"($FIELDS)\"\s*:"
82
+ # C# property style: .TIN = or .TaxId =
83
+ "\.($FIELDS)\s*="
84
+ # YAML/config style: TIN: (start of line or after whitespace)
85
+ "(^|\s)($FIELDS):\s"
86
+ )
87
+ BLOCK_MSG="BLOCKED: Output contains sensitive PII fields (TIN, SSN, bank account, etc.)"
88
+ BLOCK_HELP="For database queries: use an explicit inclusion projection listing only non-sensitive fields.
89
+ For code searches: avoid reading seed data, test fixtures, or dump files containing PII.
90
+ If you need to work with this data, use the application UI instead."
91
+ fi
51
92
 
52
93
  for PATTERN in "${SENSITIVE_PATTERNS[@]}"; do
53
94
  if echo "$OUTPUT" | grep -qE "$PATTERN"; then
54
- echo "BLOCKED: Output contains sensitive PII fields (TIN, SSN, bank account, etc.)"
55
- echo "The output includes documents or data with sensitive fields."
56
- echo "For database queries: use an explicit inclusion projection listing only non-sensitive fields."
57
- echo "For code searches: avoid reading seed data, test fixtures, or dump files containing PII."
58
- echo "If you need to work with this data, use the application UI instead."
95
+ echo "$BLOCK_MSG"
96
+ echo "$BLOCK_HELP"
59
97
  exit 2
60
98
  fi
61
99
  done
@@ -7,3 +7,4 @@
7
7
 
8
8
  echo "Reminder: If you just completed a feature implementation, run UAT before marking it done."
9
9
  echo "Ask Claude to generate a UAT checklist from the work item's acceptance criteria."
10
+ echo "Once it is merged and deployed, /qa AB#<id> runs an automated browser regression of the affected screens."
@@ -18,6 +18,7 @@ You (give task)
18
18
  ├── deployer → commits, pushes, triggers CD pipeline
19
19
  ├── db-admin → queries/fixes MongoDB data
20
20
  ├── test-runner → runs xUnit, Vitest, Playwright tests
21
+ ├── qa → drives a real browser on a deployed environment, regression-tests the affected screens
21
22
  ├── build-validator → confirms builds pass
22
23
  ├── lint-checker → runs ESLint and dotnet format
23
24
  ├── security-auditor → scans for secrets, vulnerabilities
@@ -79,7 +80,7 @@ These run automatically — no action needed:
79
80
  | Server | What It Does |
80
81
  |--------|-------------|
81
82
  | **Azure DevOps** *(core — drives every slash command)* | Work items, repos, pull requests, pipelines, wiki, test plans, advanced security |
82
- | **Playwright** | Browser testing (navigate, click, fill, screenshot) |
83
+ | **Playwright** | Browser testing (navigate, click, fill, screenshot) — **required by `/qa`** |
83
84
  | **MongoDB** | Direct database queries and updates |
84
85
  | **Microsoft Teams** | Send/read team messages and notifications |
85
86
  | **Stripe** | Payment management (when configured) |
@@ -100,10 +101,11 @@ Claude maintains persistent memory across sessions in `~/.claude/projects/.../me
100
101
  3. **Hooks guard** against secrets and bad patterns automatically
101
102
  4. **PR merges** into `main` — the compare branch. **Nothing deploys yet.** The work item stays in `Code Review` until it is promoted
102
103
  5. **Promote or release** — `/promote main dev` carries everything on `main` to Dev; `/create-release <N>` groups work items so `/deploy-release <N> test` → `staging` → `prod` can carry just those
103
- 6. **Merge each promotion PR** — that merge is what triggers the environment's CD pipeline. Reply `merged` and the command advances the work items to `Testing`, `Staging`, or `Deployed`
104
- 7. **Test** using Playwright MCP for browser testing
105
- 8. **Track** work items via the Azure DevOps MCP server
106
- 9. **Learn** — Claude saves what worked for next time
104
+ 6. **Say who verifies** — before the merge, the command asks who each product group (`COM`, `PAY`, …) should be assigned to in the target environment, offering the non-developer names already on those work items
105
+ 7. **Merge each promotion PR** — that merge triggers the environment's CD pipeline. The command watches it and, once it's green, sets the work items to `Testing`, `Staging`, or `Deployed` **and** assigns them to the approved verifiers — automatically, no reply needed
106
+ 8. **Test** using Playwright MCP for browser testing
107
+ 9. **Track** work items via the Azure DevOps MCP server
108
+ 10. **Learn** — Claude saves what worked for next time
107
109
 
108
110
  ### Story Points & Dev Ready Policy
109
111
 
@@ -159,7 +161,7 @@ feature/AB#1234-... ──PR──▸ main ──PR──▸ dev ──PR
159
161
 
160
162
  #### Work Item States ↔ Environments
161
163
 
162
- Every environment in the chain has a work item state, so the board shows where each story physically is. The **"Ready for …" states are human gates** — QA or stakeholders set them when something passes; the slash commands never do. The **environment states** (`Testing`, `Staging`, `Deployed`) are set by the promotion commands once the PR into that branch has merged.
164
+ Every environment in the chain has a work item state, so the board shows where each story physically is. The **"Ready for …" states are human gates** — QA or stakeholders set them when something passes; the slash commands never do. The **environment states** (`Testing`, `Staging`, `Deployed`) are set by the promotion commands once the PR into that branch has merged and its CD pipeline has come back green.
163
165
 
164
166
  | Branch | Environment | Gate — items should already be in… | Set once the PR into this branch merges |
165
167
  |---|---|---|---|
@@ -169,14 +171,26 @@ Every environment in the chain has a work item state, so the board shows where e
169
171
  | `staging` | Staging | `Ready for Staging` | `Staging` |
170
172
  | `prod` | Production | `Ready to Deploy` | `Deployed` |
171
173
 
174
+ `/qa` is the exception that proves the rule: it tests a work item in a browser and posts a pass/fail comment, but it **never writes a state** — not even `Ready for Testing`.
175
+
172
176
  Human-only transitions — no slash command ever makes these: `Testing → Ready for Staging` (QA sign-off), `Staging → Ready to Deploy` (stakeholder / UAT sign-off), `Deployed → Closed` (verified in production).
173
177
 
174
178
  How the commands use this table:
175
179
 
176
180
  - **Gate check before the PR.** `/deploy-release`, `/cherry-pick`, and `/promote` compare each carried work item's state against the gate for the target environment. Anything behind the gate (e.g. still `Testing` when deploying to `staging`) is flagged and the user decides whether to include it. Items *ahead* of the gate (e.g. already `Deployed` on a cherry-pick to `prod`) are reported and left alone.
177
- - **Advance after the merge, not before.** The commands can't see the PR merge, so they finish by asking you to reply `merged` once the PR has completed and the pipeline is green; only then do they write the new state — for every carried User Story, Bug, and Hot Fix, never a Feature or Task, and never backward. `/status` reports items whose state lags the branch their commits are on and offers to catch them up.
181
+ - **Advance on a green pipeline, automatically.** `/promote`, `/cherry-pick`, and `/deploy-release` do not stop at the PR. They poll it until it is `completed`, then poll the target branch's CD pipeline until every run finishes. Only on `succeeded` do they write the new state — for every carried User Story, Bug, and Hot Fix, never a Feature or Task, and never backward. A failed or canceled run changes **nothing** and is reported with the log link. `/status` reports items whose state lags the branch their commits are on and offers to catch them up.
182
+ - **Assign the verifier at the same time.** The state change hands the item to a person, so the same three commands ask **before the merge** who each group of items should go to, then apply the assignee alongside the state once the pipeline is green. See **Verifier Assignment** below.
178
183
  - **Never assume the state list.** Confirm with `mcp__azure-devops__wit_work_item` (`action: get_type`) on an unfamiliar project before writing a state name — a bad name is accepted silently by a query and rejected only at write time.
179
184
 
185
+ #### Verifier Assignment
186
+
187
+ Moving an item to `Testing`, `Staging`, or `Deployed` puts it on someone's plate, so `/promote`, `/cherry-pick`, and `/deploy-release` also change who owns it. The developer implemented it; from here on it belongs to whoever checks it in that environment.
188
+
189
+ - **Grouped by product prefix.** Work item titles are `PREFIX - Title`, so the commands group the carried items by prefix (`COM`, `PAY`, `CDA`, …) and ask **one question per group**. A staging → prod deploy carrying 2 `COM` and 2 `PAY` items asks twice; answering with the same person both times is fine, and `all 1` gives the whole batch to one person.
190
+ - **Candidates come from the work items, never from a directory.** For each group the commands read the items' own history — who set the gate state (`Ready for Staging` / `Ready to Deploy`, from `list_revisions`), who created the item, who commented on it, who last edited it — then subtract the developers (current assignee, PR author, commit authors). What's left are the non-developer names actually attached to that work, ranked with the sign-off setter first. No name is ever invented.
191
+ - **Asked before the merge, applied after.** The question comes while the PR is still in review and requires an explicit `yes`; nothing is written until the CD pipeline reports success. Then state and `System.AssignedTo` are set in the same update, and the report shows `was → now` for both so a wrong assignment is one edit to undo.
192
+ - **`Leave the current assignee alone`** is always an option — the commands never clear an assignee, and never touch a Feature's or a Task's assignee.
193
+
180
194
  #### Release Management
181
195
 
182
196
  Releases group work items together for coordinated deployment across environments. Releases are tracked in Azure DevOps as iterations (`Release #N`) and work items are tagged with `release-{N}`.
@@ -193,7 +207,7 @@ This creates a `Release #23` iteration, assigns the selected work items to it, a
193
207
  /deploy-release 23 staging
194
208
  /deploy-release 23 prod
195
209
  ```
196
- This finds all work items in Release #23, checks each one against the target environment's gate state, cherry-picks their commits into a release branch (`release/23-to-staging`), creates a PR targeting the environment branch, links all work items, and — once you reply `merged` — advances them to the environment's state (`Testing`, `Staging`, `Deployed`).
210
+ This finds all work items in Release #23, checks each one against the target environment's gate state, cherry-picks their commits into a release branch (`release/23-to-staging`), creates a PR targeting the environment branch, links all work items, asks who verifies each product group, and — once the merge's CD pipeline comes back green — advances them to the environment's state (`Testing`, `Staging`, `Deployed`) and assigns them to those verifiers.
197
211
 
198
212
  **Selective deployment:** Since releases are deployed via cherry-pick, you can deploy a full release or a subset. If staging has 5 user stories but only 3 are `Ready to Deploy`, create a release with just those 3 and deploy it.
199
213
 
@@ -213,7 +227,7 @@ Cherry-pick specific work items to an environment without a formal release:
213
227
  ```
214
228
  /cherry-pick AB#1234 AB#1235 prod
215
229
  ```
216
- This finds commits for the specified work items, checks them against the target's gate state, cherry-picks them into a branch (`cherry-pick/<date>-to-<environment>`), creates a PR, and advances the work items once you confirm the merge.
230
+ This finds commits for the specified work items, checks them against the target's gate state, cherry-picks them into a branch (`cherry-pick/<date>-to-<environment>`), creates a PR, asks who verifies each product group, and — once the merge's CD pipeline is green — advances and assigns the work items.
217
231
 
218
232
  #### Promoting Environments
219
233
 
@@ -223,7 +237,7 @@ Promote all code from one environment to the next:
223
237
  /promote staging prod
224
238
  /promote ← auto-detects source and target from current branch
225
239
  ```
226
- This creates a PR from the source branch to the target branch with a summary of all included commits and their work items, checks the work items against the target's gate state, and advances them once you confirm the merge. `main → dev` is the usual first hop after feature PRs merge.
240
+ This creates a PR from the source branch to the target branch with a summary of all included commits and their work items, checks the work items against the target's gate state, asks who verifies each product group, and — once the merge's CD pipeline is green — advances and assigns them. `main → dev` is the usual first hop after feature PRs merge.
227
241
 
228
242
  #### Rollbacks
229
243
 
@@ -288,13 +302,14 @@ All deployment and release operations are available as slash commands:
288
302
  | `/resolve-feedback` | `/resolve-feedback 142` | Address unresolved PR comment threads, push fixes, reply + resolve threads |
289
303
  | `/deploy` | `/deploy "commit message"` | Commit, push, trigger pipeline (only on `dev`/`test`/`staging`/`prod` — never on `main`) |
290
304
  | `/create-release` | `/create-release 23` | Group work items into Release #23 |
291
- | `/deploy-release` | `/deploy-release 23 staging` | Gate-check → cherry-pick release to environment → PR → advance states after merge |
305
+ | `/deploy-release` | `/deploy-release 23 staging` | Gate-check → cherry-pick release to environment → PR → ask who verifies → advance + assign on a green pipeline |
292
306
  | `/add-to-release` | `/add-to-release 24 AB#4599` | Add work items to existing release |
293
- | `/cherry-pick` | `/cherry-pick AB#1234 AB#1235 prod` | Gate-check → cherry-pick specific work items → PR → advance states after merge |
294
- | `/promote` | `/promote main dev` | Promote all code between environments (`main → dev → test → staging → prod`) → advance states after merge |
307
+ | `/cherry-pick` | `/cherry-pick AB#1234 AB#1235 prod` | Gate-check → cherry-pick specific work items → PR → ask who verifies → advance + assign on a green pipeline |
308
+ | `/promote` | `/promote main dev` | Promote all code between environments (`main → dev → test → staging → prod`) → ask who verifies → advance + assign on a green pipeline |
295
309
  | `/rollback` | `/rollback AB#1234 prod` | Revert commits on an environment |
296
310
  | `/status` | `/status release 24` | Check release, pipeline, environment, or work item status; flags work items whose state lags their environment |
297
311
  | `/where` | `/where AB#1234` | Show which environment branches contain a work item's commits |
312
+ | `/qa` | `/qa AB#1234 [env]` | Verify the item is fully deployed with a green pipeline → open the app in a real browser → sign in as a test account → full regression of the screens the story touched → pass/fail comment (never a state change) |
298
313
  | `/plan-backlog` | `/plan-backlog [project]` | Sweep backlog for Dev Ready stories with points and no tasks → propose child tasks with hours |
299
314
  | `/plan-sprint` | `/plan-sprint [project]` | Sweep the current sprint for stories/bugs with no child tasks → propose one child task with hours per item |
300
315
  | `/quote-backlog` | `/quote-backlog [project]` | Sweep backlog for unpointed items ready to estimate — stories in `Design Approved`, **bugs in `New`** (bugs have no design states) → review completeness, check for duplicates, suggest rewrites, propose points + creator comments (10 at a time, approval-gated). Pointed items move to Dev Ready; stories that can't be quoted move back to **Design Review**, bugs that can't be quoted get a **`needs-info`** tag, so the next sweep skips them. Every run also audits the drop-out queue (tagged bugs **and** stories bounced to Design Review) and reports any whose creator answered but which nobody returned to the sweep — neither mechanism expires, so those are otherwise invisible forever |
@@ -364,6 +379,53 @@ Glasswing and Monarch (not yet migrated — 1 environment):
364
379
  | develop | Dev | CD - Development (28) |
365
380
  ```
366
381
 
382
+ ### Environment URLs
383
+
384
+ `/qa` opens the app in a real browser, so it needs to know where each environment is hosted and which test account to sign in as. Without this section `/qa` reports and stops rather than guessing a hostname.
385
+
386
+ Add this to your project's `CLAUDE.md` — **above the `## Claude Kit Workflow` heading.** Everything from that heading to the end of the file is replaced on each kit update, so configuration placed below it is lost.
387
+
388
+ ```markdown
389
+ ## Environment URLs
390
+
391
+ | Branch | Environment | App URL | API URL |
392
+ |--------|------------|---------|---------|
393
+ | (none) | local | http://localhost:5173 | https://localhost:7001 |
394
+ | dev | Dev | https://dev.myapp.example.com | https://dev.api.myapp.example.com |
395
+ | test | Test | https://test.myapp.example.com | https://test.api.myapp.example.com |
396
+ | staging | Staging | https://staging.myapp.example.com | https://staging.api.myapp.example.com |
397
+
398
+ **Sign-in:** form at `/login` — app-issued JWT, no SSO. Signed-in landing: `/dashboard`.
399
+
400
+ **Test accounts** — values live in environment variables; never write a password here.
401
+
402
+ | Role | Username variable | Password variable | Default |
403
+ |------|-------------------|-------------------|---------|
404
+ | Admin | `MYAPP_QA_ADMIN_USERNAME` | `MYAPP_QA_ADMIN_PASSWORD` | yes |
405
+ | Provider | `MYAPP_QA_PROVIDER_USERNAME` | `MYAPP_QA_PROVIDER_PASSWORD` | |
406
+ ```
407
+
408
+ **Rules for slash commands:**
409
+ - **Branch names must match the `## Pipeline Configuration` table** row for row. The two tables are joined on the branch column — `/qa` reads the URL here and the pipeline ID there. A mismatch is a configuration error worth reporting.
410
+ - **There is no `prod` row, deliberately.** `/qa` refuses production: an unattended regression pass signs in and clicks through live member data.
411
+ - **Include the rows below the one you test.** The row immediately below the target serves the pre-change build, which is how `/qa` tells a regression this story caused from a bug that was already there.
412
+ - **App URL is required; API URL is optional** — it is used to attribute a failing network call to the backend rather than the screen.
413
+ - **`Sign-in:`** is optional prose. Include it when the login route is not `/login`, when the landing page is not obvious, or — importantly — when sign-in goes through **SSO / Entra ID with MFA**. Say so, and `/qa` reports that it cannot sign in unattended instead of hanging on a browser prompt.
414
+ - **Test accounts** are required by `/qa` and nothing else. The `Default` column marks the account used when `--role` is not given; if no row is marked, the first row wins. A second role lets `/qa` verify that restricted controls are correctly *absent* — a class of bug a manual checklist rarely catches.
415
+ - **Never put a credential in this table.** Only variable names. The values belong in your shell profile — see Environment Setup below.
416
+
417
+ **Example from an actual project:**
418
+
419
+ COMPASS:
420
+ ```markdown
421
+ | Branch | Environment | App URL | API URL |
422
+ |--------|------------|---------|---------|
423
+ | (none) | local | http://localhost:5173 | https://localhost:7169 |
424
+ | dev | Dev | https://dev.compass.caresolutions.com | https://dev.api.compass.caresolutions.com |
425
+ | test | Test | https://test.compass.caresolutions.com | https://test.api.compass.caresolutions.com |
426
+ | staging | Staging | https://staging.compass.caresolutions.com | https://staging.api.compass.caresolutions.com |
427
+ ```
428
+
367
429
  ### Environment Setup
368
430
 
369
431
  Each team member needs to set their own environment variables (never commit these):
@@ -380,4 +442,15 @@ az login
380
442
 
381
443
  # Microsoft Teams MCP — no env vars needed. First invocation prints a
382
444
  # device code + URL to sign in via Microsoft Graph (OAuth device flow).
445
+
446
+ # QA test accounts (required for /qa — browser testing on a deployed environment).
447
+ # One pair per role. The variable NAMES are declared in the project's
448
+ # `## Environment URLs` section; the VALUES live only here, never in any file.
449
+ # Use throwaway QA accounts with least privilege, never a personal login.
450
+ export COMPASS_QA_ADMIN_USERNAME="qa-admin@example.com"
451
+ export COMPASS_QA_ADMIN_PASSWORD="..."
452
+ export COMPASS_QA_PROVIDER_USERNAME="qa-provider@example.com"
453
+ export COMPASS_QA_PROVIDER_PASSWORD="..."
383
454
  ```
455
+
456
+ `/qa` reads these itself **inside a subagent** and fills the login form directly. It never prints a value, never writes one to a file, and never includes one in a report or a work item comment. If your app's sign-in requires MFA or SSO, unattended login is not possible — say so in the `Sign-in:` line of `## Environment URLs` and `/qa` will report that instead of hanging on a browser prompt.