@roopesh.yadava/qa-pack 1.5.1 → 1.6.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/README.md CHANGED
@@ -107,6 +107,28 @@ token beyond the one line of output. This is also why `qa-insights` (dashboard/d
107
107
  across every product you've ever tested) costs about the same whether you have 2 products or
108
108
  200 — the script does the aggregation, not the model.
109
109
 
110
+ ### Team dashboard (optional, off by default)
111
+
112
+ `qa-insights`' dashboard is local and per-repo — it only sees runs on your own machine.
113
+ To see the whole team's runs across every product, set `QA_DASHBOARD_REPO` and
114
+ `QA_DASHBOARD_TOKEN` in `.env` (see `.env.example`). Once set, **every skill** reports its
115
+ own execution as its last step — one small JSON file pushed to that shared GitHub repo via
116
+ the GitHub Contents API. No local `git commit`/`git push`, so it never touches this repo's
117
+ `.git` or shows up in `git status`. Leave the variables unset and nothing changes; the
118
+ call is a silent no-op, same as the optional `QA_TRACKING_DIR` token tracking.
119
+
120
+ One record is one *skill execution*, not one card: a single card taken through `qa-agent` →
121
+ `manual-testing` → `bug-reporting` reports three. The dashboard counts both ("skill runs"
122
+ and "cards covered") so neither number is misleading.
123
+
124
+ What each record contains: product, card (when the skill has one), skill name, outcome, bug
125
+ IDs, reuse %, date, calling repo name, and an author label taken from `git config user.name`
126
+ — never `user.email`, since the dashboard repo is typically public and its history is
127
+ permanent. Set `QA_DASHBOARD_AUTHOR` in `.env` to override the label. A push that fails
128
+ (expired PAT, wrong repo, no network) stays silent and is appended to
129
+ `outputs/report-run-errors.log`; add `--verbose` to a manual `report-run` call to see the
130
+ result directly, which is the way to verify a freshly issued token.
131
+
110
132
  ## What postinstall does
111
133
 
112
134
  | File | Behaviour |
@@ -96,6 +96,7 @@ node .claude/skills/qa-agent/toolkit/qa-toolkit.cjs <command> [--flags]
96
96
  | `trust-record` / `trust-status` --product P [--namespace N] | automation, mobile-automation (`--namespace mobile` — routes to a separate `trust.mobile.json` so its gate streaks never enter qa-agent's web-only eligibility computation), qa-agent (status) | Trust ratchet on Gate 1/Gate 2 approvals. `--namespace` is optional and additive — omitted, behavior is unchanged (`trust.json`); `trust-status` takes `min(streak)` across every gate in whichever file it reads, so gates from two automation surfaces must never share one file |
97
97
  | `locator-record` / `locator-query` --product P --page U | automation, mobile-automation (page keys prefixed `mobile:` to avoid colliding with web page URLs) | Self-improving locator memory across self-heal fixes |
98
98
  | `dashboard` / `digest [--days N]` / `roi` | qa-insights | Cross-product reports — 100% script-generated, zero synthesis. Add `--json` to any of the three for a structured payload (same data, machine-readable) instead of the one-line summary — for anything scripting against this (e.g. an external dashboard app) rather than chatting with it. |
99
+ | `report-run` --skill S --outcome Y [--product P] [--card C] [--bugs "a,b"] [--reuse N] [--phase X] [--verbose] | **every skill, as its own last step** — `qa-agent`, `automation`, `manual-testing`, `mobile-automation`, `accessibility-testing`, `ui-test-figma`, `roam-testing`, `bug-reporting`, `impacted-tests`, `test-charter`, `k6-framework-scaffold` | Optional team dashboard: pushes one JSON file per **skill execution** to a shared GitHub repo (Contents API — no local git commit/push). Only `--skill`/`--outcome` are required: `--product` defaults to the calling repo's name and `--card` is omitted entirely by the skills that have no Jira card. Silent no-op unless `QA_DASHBOARD_REPO`/`QA_DASHBOARD_TOKEN` are set in `.env`, same convention as `QA_TRACKING_DIR`. A failed push never prints or fails the run — it is appended to `outputs/report-run-errors.log`; `--verbose` prints the result, which is how you verify a new PAT once. No `--notes` flag — only short, structurally-constrained values are safe to inline. |
99
100
 
100
101
  This file is versioned logic (always overwritten on `npm update`, like every other skill
101
102
  file) — never store product data inside it. Its outputs live under
@@ -103,6 +104,11 @@ file) — never store product data inside it. Its outputs live under
103
104
  `locator-learnings.md` — all new, all gitignored the same way `context.md` already is) or
104
105
  `outputs/` (`dashboard.html`, `qa-weekly-digest-*.md`, `roi-report.md`).
105
106
 
107
+ The toolkit reads `.env` from the repo root itself (real environment variables win), so
108
+ every `QA_*` value documented for `.env` reaches it without the calling skill exporting
109
+ anything. Before this, `QA_DASHBOARD_*` and `QA_TRACKING_DIR` were read straight off
110
+ `process.env` and were therefore permanently unset in normal skill use.
111
+
106
112
  **Never inline arbitrary text into a toolkit shell call.** Bug descriptions, locator
107
113
  strings, and anything else that isn't a short agent-controlled token (a card ID, a product
108
114
  folder name, a URL) can contain quotes, `` ` ``, `$(...)`, or `|` — inlined into a bash
@@ -294,6 +300,8 @@ First run will always be 0% (cold cache). Second and subsequent runs should cach
294
300
  | Token analytics | `~/.claude/token_analytics.png` |
295
301
  | Knowledge graph | `graphify-out/graph.html` (open in browser) |
296
302
  | Product QA context | `.claude/skills/qa-agent/product_context/[PRODUCT]/context.md` |
303
+ | Team QA dashboard data (shared, cross-repo, not in this repo) | `runs/[PRODUCT]/[date]-[card]-[skill]-[id].json` inside the repo named by `QA_DASHBOARD_REPO` — see `report-run` |
304
+ | Team dashboard push failures (local, gitignored) | `outputs/report-run-errors.log` — the only place a failed `report-run` is visible |
297
305
  | DOM fingerprint cache | `.claude/skills/qa-agent/product_context/[PRODUCT]/dom-fingerprints.json` |
298
306
  | Trust ratchet state | `.claude/skills/qa-agent/product_context/[PRODUCT]/trust.json` |
299
307
  | Locator learnings | `.claude/skills/qa-agent/product_context/[PRODUCT]/locator-learnings.md` |
@@ -315,3 +315,19 @@ Accessibility Testing Complete
315
315
  | Jira bug creation fails | Note failure, continue remaining bugs, report failures at end |
316
316
  | OTP screen appears unexpectedly | Type the `QA_OTP_CODE` value from `.env`, proceed |
317
317
  | Page requires further navigation after login | Follow redirect, confirm TARGET_URL loads |
318
+
319
+ ---
320
+
321
+ ## Final Step — Report to team dashboard (optional, silent)
322
+
323
+ Always the last action of the run, after everything else has printed. This is a no-op
324
+ unless `QA_DASHBOARD_REPO`/`QA_DASHBOARD_TOKEN` are set in `.env` — never print anything
325
+ about it, never retry, never treat a non-zero exit as a run failure.
326
+
327
+ ```
328
+ node .claude/skills/qa-agent/toolkit/qa-toolkit.cjs report-run \
329
+ --skill accessibility-testing --card {JIRA_CARD_ID, omit if none} \
330
+ --outcome {Pass|Fail|Partial} --bugs "{comma-separated bug IDs, or omit if none}"
331
+ ```
332
+ This skill has no `PRODUCT_FOLDER`; omit `--product` and the record is filed under the
333
+ calling repo's name automatically. Omit `--card` too when the run had no Jira card.
@@ -531,3 +531,21 @@ Print one line only (omit entirely if file not found):
531
531
  ```
532
532
  Selectors written to product context ({N} elements, {N} flows).
533
533
  ```
534
+
535
+ ---
536
+
537
+ ## Final Step — Report to team dashboard (optional, silent)
538
+
539
+ Always the last action of the run, after everything else has printed. This is a no-op
540
+ unless `QA_DASHBOARD_REPO`/`QA_DASHBOARD_TOKEN` are set in `.env` — never print anything
541
+ about it, never retry, never treat a non-zero exit as a run failure.
542
+
543
+ ```
544
+ node .claude/skills/qa-agent/toolkit/qa-toolkit.cjs report-run \
545
+ --skill automation --product {PRODUCT_FOLDER} --card {CARD_ID} \
546
+ --outcome {Pass|Fail|Partial} --reuse {reuse % number} --phase Automation
547
+ ```
548
+ `report-run` takes only short, structurally-constrained values (product folder, card ID,
549
+ skill name, outcome, bug IDs). It has no `--notes` flag — never inline freeform text such as
550
+ a finding, a page title, or a bug description (see "Never inline arbitrary text" in
551
+ `SKILLS_CONTEXT.md`).
@@ -293,4 +293,21 @@ Show a summary:
293
293
  - Notified: [Confirmed person's name]
294
294
  - Screenshots attached: [list of filenames, or "none" / "credentials not set"]
295
295
 
296
- ---
296
+ ---
297
+
298
+ ## Final Step — Report to team dashboard (optional, silent)
299
+
300
+ Always the last action of the run, after everything else has printed. This is a no-op
301
+ unless `QA_DASHBOARD_REPO`/`QA_DASHBOARD_TOKEN` are set in `.env` — never print anything
302
+ about it, never retry, never treat a non-zero exit as a run failure.
303
+
304
+ ```
305
+ node .claude/skills/qa-agent/toolkit/qa-toolkit.cjs report-run \
306
+ --skill bug-reporting --product {PRODUCT_FOLDER} --card {the card just filed/updated} \
307
+ --outcome {Pass if the bug posted, Fail if it did not} \
308
+ --bugs "{the bug key(s) filed}"
309
+ ```
310
+ `report-run` takes only short, structurally-constrained values (product folder, card ID,
311
+ skill name, outcome, bug IDs). It has no `--notes` flag — never inline freeform text such as
312
+ a finding, a page title, or a bug description (see "Never inline arbitrary text" in
313
+ `SKILLS_CONTEXT.md`).
@@ -108,4 +108,19 @@ Rules for each entry:
108
108
 
109
109
  ## Step 5 — Stop
110
110
 
111
- The report is the deliverable. Do NOT run any test, do NOT ask whether to run them — the QA reviews the report and runs files manually using the footer command. End the turn after printing the report.
111
+ The report is the deliverable. Do NOT run any test, do NOT ask whether to run them — the QA reviews the report and runs files manually using the footer command. End the turn after printing the report (the dashboard report below is the one exception — it prints nothing).
112
+
113
+ ---
114
+
115
+ ## Final Step — Report to team dashboard (optional, silent)
116
+
117
+ Run this after the report is printed and before ending the turn. This is a no-op
118
+ unless `QA_DASHBOARD_REPO`/`QA_DASHBOARD_TOKEN` are set in `.env` — never print anything
119
+ about it, never retry, never treat a non-zero exit as a run failure.
120
+
121
+ ```
122
+ node .claude/skills/qa-agent/toolkit/qa-toolkit.cjs report-run \
123
+ --skill impacted-tests --outcome Pass
124
+ ```
125
+ No product folder and no card in this skill — omit both flags; the record is filed under
126
+ the calling repo's name.
@@ -56,3 +56,17 @@ After it runs, tell the tester the next steps:
56
56
  - Keep real tokens out of version control (the scaffold's `.gitignore` covers `secrets.local.*`).
57
57
  - If the user wants the structure tailored (their user types, real endpoints from a Postman
58
58
  collection), scaffold first, then edit the generated templates to match.
59
+
60
+ ---
61
+
62
+ ## Final Step — Report to team dashboard (optional, silent)
63
+
64
+ Always the last action of the run, after everything else has printed. This is a no-op
65
+ unless `QA_DASHBOARD_REPO`/`QA_DASHBOARD_TOKEN` are set in `.env` — never print anything
66
+ about it, never retry, never treat a non-zero exit as a run failure.
67
+
68
+ ```
69
+ node .claude/skills/qa-agent/toolkit/qa-toolkit.cjs report-run \
70
+ --skill k6-framework-scaffold --outcome {Pass|Fail}
71
+ ```
72
+ No product folder and no card — omit both flags.
@@ -627,3 +627,22 @@ Do NOT stream: full charter text, all test results (save to file), full bug repo
627
627
  | Figma URL missing / user says no | Skip UI Testing, note in final summary |
628
628
  | `browser_evaluate` returns null for element | Log "element not captured" in hints, continue |
629
629
  | Bug creation fails | Note failure, continue with remaining bugs, report at end |
630
+
631
+ ---
632
+
633
+ ## Final Step — Report to team dashboard (optional, silent)
634
+
635
+ Always the last action of the run, after everything else has printed. This is a no-op
636
+ unless `QA_DASHBOARD_REPO`/`QA_DASHBOARD_TOKEN` are set in `.env` — never print anything
637
+ about it, never retry, never treat a non-zero exit as a run failure.
638
+
639
+ ```
640
+ node .claude/skills/qa-agent/toolkit/qa-toolkit.cjs report-run \
641
+ --skill manual-testing --product {PRODUCT_FOLDER} --card {CARD_ID} \
642
+ --outcome {Pass|Fail|Partial} --bugs "{comma-separated bug IDs, or omit if none}" \
643
+ --phase Manual
644
+ ```
645
+ `report-run` takes only short, structurally-constrained values (product folder, card ID,
646
+ skill name, outcome, bug IDs). It has no `--notes` flag — never inline freeform text such as
647
+ a finding, a page title, or a bug description (see "Never inline arbitrary text" in
648
+ `SKILLS_CONTEXT.md`).
@@ -591,3 +591,21 @@ One-line confirmation either way:
591
591
  ```
592
592
  Mobile context saved → .claude/skills/qa-agent/product_context/{PRODUCT_FOLDER}/context.md ({N} elements, {N} flows).
593
593
  ```
594
+
595
+ **Then report to the team dashboard (optional, silent)** — using the same values just
596
+ written to the Runs Log row:
597
+
598
+ ```
599
+ node .claude/skills/qa-agent/toolkit/qa-toolkit.cjs report-run \
600
+ --skill mobile-automation --product {PRODUCT_FOLDER} --card {CARD_ID} \
601
+ --phase Automation \
602
+ --outcome {OUTCOME} --bugs "{comma-separated bug IDs or empty}" \
603
+ --reuse {reuse % number}
604
+ ```
605
+
606
+ Only pass `--product`/`--card`/`--phase`/`--outcome`/`--bugs`/`--reuse` — short,
607
+ structurally-constrained values (see "Never inline arbitrary text" in
608
+ `SKILLS_CONTEXT.md`). `report-run` has no `--notes` flag; don't inline the freeform
609
+ Runs Log note here. This is a no-op unless `QA_DASHBOARD_REPO`/`QA_DASHBOARD_TOKEN` are
610
+ set in `.env` — never print anything about it, never retry, never treat a non-zero exit
611
+ as a run failure.
@@ -370,6 +370,21 @@ Write `context.md`, then print one line:
370
370
  Product context saved → .claude/skills/qa-agent/product_context/{PRODUCT_FOLDER}/context.md
371
371
  ```
372
372
 
373
+ ### 6f — Report to team dashboard (optional, silent)
374
+
375
+ Immediately after 6e, using the same values just written to the Runs Log row, shell out:
376
+
377
+ ```
378
+ node .claude/skills/qa-agent/toolkit/qa-toolkit.cjs report-run \
379
+ --skill qa-agent --product {PRODUCT_FOLDER} --card {CARD_ID} \
380
+ --phase {Manual|Automation|Both} \
381
+ --outcome {OUTCOME} --bugs "{comma-separated bug IDs or empty}" \
382
+ --reuse {reuse % number, omit for manual-only runs}
383
+ ```
384
+
385
+ This is a no-op unless `QA_DASHBOARD_REPO`/`QA_DASHBOARD_TOKEN` are set in `.env` — never
386
+ print anything about it, never retry, never treat a non-zero exit as a run failure.
387
+
373
388
  ---
374
389
 
375
390
  ## Post-run Cleanup (all phases)
@@ -17,11 +17,35 @@
17
17
  */
18
18
 
19
19
  const fs = require('fs');
20
+ const os = require('os');
20
21
  const path = require('path');
21
22
  const crypto = require('crypto');
22
23
  const { spawnSync } = require('child_process');
23
24
 
24
25
  const ROOT = process.cwd();
26
+
27
+ // ── .env loading ─────────────────────────────────────────────────────────
28
+ // Every doc in the pack tells users to put QA_* config in `.env`, but this is a
29
+ // plain `node` script invoked by skills via Bash — nothing exports that file into
30
+ // the environment, and the pack ships no dotenv. Without this, every
31
+ // `process.env.QA_*` read below is permanently undefined and the features gated
32
+ // on them (report-run, token tracking) are silent no-ops no matter what the user
33
+ // configures. Real env vars always win, so an explicit `export` still overrides.
34
+ function loadDotEnv() {
35
+ let raw;
36
+ try { raw = fs.readFileSync(path.join(ROOT, '.env'), 'utf8'); } catch { return; }
37
+ for (const line of raw.split(/\r?\n/)) {
38
+ const m = /^\s*(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*)$/.exec(line);
39
+ if (!m) continue; // blank line, comment, or malformed — skip
40
+ let val = m[2].trim();
41
+ // strip one layer of matching quotes; leave inner content untouched
42
+ if (val.length >= 2 && ((val[0] === '"' && val.endsWith('"')) || (val[0] === "'" && val.endsWith("'")))) {
43
+ val = val.slice(1, -1);
44
+ }
45
+ if (process.env[m[1]] === undefined) process.env[m[1]] = val;
46
+ }
47
+ }
48
+ loadDotEnv();
25
49
  const PRODUCT_CONTEXT_ROOT = path.join(ROOT, '.claude', 'skills', 'qa-agent', 'product_context');
26
50
  const OUTPUTS_DIR = path.join(ROOT, 'outputs');
27
51
  const TRUST_THRESHOLD = 5;
@@ -719,6 +743,109 @@ function cmdRoi(args) {
719
743
  );
720
744
  }
721
745
 
746
+ // ── report-run: optional team dashboard reporting (git-based, silent opt-in) ──
747
+ //
748
+ // Mirrors the QA_TRACKING_DIR convention (see SKILLS_CONTEXT.md "Token Tracking"):
749
+ // unset config = silent no-op, never mention it, never retry, never break the run.
750
+ // When configured, pushes one JSON file per run to a shared GitHub repo via the
751
+ // Contents API (a plain create — no local git commit/push in the calling repo, no
752
+ // read-modify-write race with other teammates' concurrent runs).
753
+
754
+ function gitConfigValue(key) {
755
+ const res = spawnSync('git', ['config', key], { cwd: ROOT, encoding: 'utf8' });
756
+ return res.status === 0 ? res.stdout.trim() : '';
757
+ }
758
+
759
+ function currentRepoName() {
760
+ const remote = spawnSync('git', ['remote', 'get-url', 'origin'], { cwd: ROOT, encoding: 'utf8' });
761
+ if (remote.status === 0 && remote.stdout.trim()) {
762
+ return remote.stdout.trim().replace(/\.git$/, '').split(/[/:]/).pop();
763
+ }
764
+ return path.basename(ROOT);
765
+ }
766
+
767
+ async function cmdReportRun(args) {
768
+ const repo = process.env.QA_DASHBOARD_REPO;
769
+ const token = process.env.QA_DASHBOARD_TOKEN;
770
+ if (!repo || !token) return; // team dashboard not configured — silent no-op
771
+
772
+ // Only --skill and --outcome are universal. Half the skills in the pack have no
773
+ // Jira card at all (impacted-tests, k6-framework-scaffold, test-charter, and
774
+ // roam-testing when run without one) and some have no product context, so
775
+ // requiring either would make "log every skill" impossible to satisfy.
776
+ requireArgs(args, ['skill', 'outcome']);
777
+
778
+ // No freeform --notes flag: only short, structurally-constrained values (product/card/
779
+ // phase/outcome/bug IDs) are safe to inline into the caller's shell command — see the
780
+ // "Never inline arbitrary text" rule in SKILLS_CONTEXT.md.
781
+ const payload = {
782
+ product: args.product || currentRepoName(),
783
+ card: args.card || null,
784
+ // One record per skill execution. `skill` is what ran (manual-testing,
785
+ // automation, ...); `phase` stays for backward compat with records already
786
+ // in the dashboard repo, defaulting to the skill name when a caller omits it.
787
+ skill: args.skill,
788
+ phase: args.phase || args.skill,
789
+ outcome: args.outcome,
790
+ bugsFiled: String(args.bugs || '').split(',').map((s) => s.trim()).filter(Boolean),
791
+ reusePct: args.reuse !== undefined ? parseFloat(args.reuse) : null,
792
+ date: args.date || todayStr(),
793
+ // Display name only — never user.email. The dashboard repo is public, so an
794
+ // email here is published permanently in git history; the "runs by teammate"
795
+ // panel only ever needs a label. QA_DASHBOARD_AUTHOR overrides.
796
+ author: process.env.QA_DASHBOARD_AUTHOR || gitConfigValue('user.name') || os.userInfo().username,
797
+ repo: currentRepoName(),
798
+ reportedAt: new Date().toISOString(),
799
+ };
800
+
801
+ // These land in a URL path, so restrict them to a safe charset rather than
802
+ // trusting the caller — a stray `/` or `..` in a product name would otherwise
803
+ // write outside runs/ in the dashboard repo.
804
+ const seg = (v, fallback) => String(v || fallback).replace(/[^A-Za-z0-9._-]/g, '-').replace(/^[.-]+/, '') || fallback;
805
+ const filePath = `runs/${seg(payload.product, 'unknown')}/${payload.date}-${seg(payload.card, 'no-card')}-${seg(payload.skill, 'skill')}-${crypto.randomBytes(4).toString('hex')}.json`;
806
+ const content = Buffer.from(JSON.stringify(payload, null, 2)).toString('base64');
807
+
808
+ const controller = new AbortController();
809
+ const timeout = setTimeout(() => controller.abort(), 5000);
810
+ try {
811
+ const res = await fetch(`https://api.github.com/repos/${repo}/contents/${filePath}`, {
812
+ method: 'PUT',
813
+ signal: controller.signal,
814
+ headers: {
815
+ Authorization: `Bearer ${token}`,
816
+ Accept: 'application/vnd.github+json',
817
+ 'Content-Type': 'application/json',
818
+ 'User-Agent': 'qa-pack-toolkit',
819
+ },
820
+ body: JSON.stringify({ message: `run: ${payload.product} ${payload.card}`, content }),
821
+ });
822
+ // A 401 (dead/expired PAT — fine-grained tokens cap at 1 year), a 404 (wrong
823
+ // QA_DASHBOARD_REPO, or a token without Contents:write) and a success are all
824
+ // indistinguishable if the status is never read. Stay silent on stdout so a
825
+ // broken dashboard can't derail a QA run, but always leave a trace on disk.
826
+ if (!res.ok) reportRunFailure(args, `HTTP ${res.status} ${res.statusText} — ${(await res.text().catch(() => '')).slice(0, 200)}`);
827
+ else if (args.verbose) console.log(`report-run OK → ${repo}/${filePath}`);
828
+ } catch (err) {
829
+ reportRunFailure(args, err && err.name === 'AbortError' ? 'timed out after 5s' : String(err && err.message || err));
830
+ } finally {
831
+ clearTimeout(timeout);
832
+ }
833
+ }
834
+
835
+ // Failure trail for report-run. stdout stays clean (the calling skill must never
836
+ // print or retry), so the evidence goes to outputs/ — already gitignored — and to
837
+ // stdout only under --verbose, which is how you verify a fresh PAT once.
838
+ function reportRunFailure(args, detail) {
839
+ const line = `${new Date().toISOString()} report-run failed: ${detail}\n`;
840
+ if (args.verbose) console.log(line.trim());
841
+ try {
842
+ ensureDir(OUTPUTS_DIR);
843
+ fs.appendFileSync(path.join(OUTPUTS_DIR, 'report-run-errors.log'), line);
844
+ } catch {
845
+ // last resort: swallow — a dashboard problem must never fail a QA run
846
+ }
847
+ }
848
+
722
849
  // ── dispatch ────────────────────────────────────────────────────────────
723
850
 
724
851
  function main() {
@@ -741,6 +868,7 @@ function main() {
741
868
  dashboard: cmdDashboard,
742
869
  digest: cmdDigest,
743
870
  roi: cmdRoi,
871
+ 'report-run': cmdReportRun,
744
872
  };
745
873
  const handler = handlers[command];
746
874
  if (!handler) {
@@ -748,11 +876,11 @@ function main() {
748
876
  'Usage: qa-toolkit.cjs <command> [--flags]\n' +
749
877
  'Commands: list-products, get-bugs, get-runs, get-selectors, fingerprint, pii-scan, dup-bug,\n' +
750
878
  ' cost-estimate, risk-score, trust-record, trust-status,\n' +
751
- ' locator-record, locator-query, dashboard, digest, roi'
879
+ ' locator-record, locator-query, dashboard, digest, roi, report-run'
752
880
  );
753
881
  process.exit(command ? 1 : 0);
754
882
  }
755
- handler(args);
883
+ Promise.resolve(handler(args)).catch(() => {});
756
884
  }
757
885
 
758
886
  main();
@@ -185,3 +185,22 @@ If yes: `Skill: test-charter`, pre-filled with the roam report path just saved.
185
185
  | A page 500s on load | Record as 🔴, do not retry, continue the loop |
186
186
  | Page cap hit with queue still non-empty | Stop cleanly, report `{visited} of {discovered}` coverage — never silently keep going |
187
187
  | `browser_network_requests` unavailable | Skip failed-request detection for that page, note it once in the report, continue |
188
+
189
+ ---
190
+
191
+ ## Final Step — Report to team dashboard (optional, silent)
192
+
193
+ Always the last action of the run, after everything else has printed. This is a no-op
194
+ unless `QA_DASHBOARD_REPO`/`QA_DASHBOARD_TOKEN` are set in `.env` — never print anything
195
+ about it, never retry, never treat a non-zero exit as a run failure.
196
+
197
+ ```
198
+ node .claude/skills/qa-agent/toolkit/qa-toolkit.cjs report-run \
199
+ --skill roam-testing --product {PRODUCT_FOLDER} --card {CARD_ID, omit if none} \
200
+ --outcome {Pass|Fail|Partial} --bugs "{comma-separated bug IDs, or omit if none}"
201
+ ```
202
+ A card is optional in this skill (Step 0 allows `skip`) — omit `--card` entirely when
203
+ there isn't one rather than passing a placeholder. `report-run` takes only short, structurally-constrained values (product folder, card ID,
204
+ skill name, outcome, bug IDs). It has no `--notes` flag — never inline freeform text such as
205
+ a finding, a page title, or a bug description (see "Never inline arbitrary text" in
206
+ `SKILLS_CONTEXT.md`).
@@ -298,3 +298,17 @@ TEST CHARTER PUBLISHED
298
298
  Published: [published_url or "URL not returned by API"]
299
299
  ✅ Uploaded to decision record
300
300
  ```
301
+
302
+ ---
303
+
304
+ ## Final Step — Report to team dashboard (optional, silent)
305
+
306
+ Always the last action of the run, after everything else has printed. This is a no-op
307
+ unless `QA_DASHBOARD_REPO`/`QA_DASHBOARD_TOKEN` are set in `.env` — never print anything
308
+ about it, never retry, never treat a non-zero exit as a run failure.
309
+
310
+ ```
311
+ node .claude/skills/qa-agent/toolkit/qa-toolkit.cjs report-run \
312
+ --skill test-charter --outcome {Pass|Fail}
313
+ ```
314
+ No product folder and no card — omit both flags.
@@ -236,3 +236,18 @@ JIRA UPDATED
236
236
  ```
237
237
 
238
238
  Run `end + report + session` token close-out.
239
+
240
+ ---
241
+
242
+ ## Final Step — Report to team dashboard (optional, silent)
243
+
244
+ Always the last action of the run, after everything else has printed. This is a no-op
245
+ unless `QA_DASHBOARD_REPO`/`QA_DASHBOARD_TOKEN` are set in `.env` — never print anything
246
+ about it, never retry, never treat a non-zero exit as a run failure.
247
+
248
+ ```
249
+ node .claude/skills/qa-agent/toolkit/qa-toolkit.cjs report-run \
250
+ --skill ui-test-figma --card {CARD_ID, omit if none} \
251
+ --outcome {Pass|Fail|Partial} --bugs "{comma-separated bug IDs, or omit if none}"
252
+ ```
253
+ Omit `--product` (no product folder in this skill) and omit `--card` when no card was given.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@roopesh.yadava/qa-pack",
3
- "version": "1.5.1",
3
+ "version": "1.6.0",
4
4
  "description": "AI-powered QA agent skills for Claude Code — manual testing, BDD automation, accessibility, UI/Figma diff, bug reporting",
5
5
  "scripts": {
6
6
  "postinstall": "node bin/postinstall.js"
@@ -24,3 +24,16 @@ JIRA_API_TOKEN=
24
24
  # ── Optional: model-tier recommendation for qa-agent (see SKILLS_CONTEXT.md) ──
25
25
  # recommend (default) | off | subagent (advanced — see "Model Routing" in SKILLS_CONTEXT.md)
26
26
  # QA_MODEL_ROUTING=recommend
27
+
28
+ # ── Optional: team QA dashboard (shared, git-based, no server to host) ────────
29
+ # Point at the shared dashboard-data repo ("owner/repo") + a fine-grained PAT
30
+ # scoped to only that repo with Contents: Read and write access.
31
+ # When either is unset, runs are skipped silently — nothing is sent anywhere.
32
+ # Once set, EVERY skill reports its own execution as its last step.
33
+ # Note: if the dashboard repo is public, everything below is published permanently
34
+ # in its git history — product folder names, Jira card IDs, bug IDs and the author
35
+ # label. The author label is git config user.name (never your email); override it
36
+ # here if you'd rather publish something else, e.g. a first name or initials.
37
+ # QA_DASHBOARD_REPO=your-org/qa-dashboard-data
38
+ # QA_DASHBOARD_TOKEN=
39
+ # QA_DASHBOARD_AUTHOR=