@roopesh.yadava/qa-pack 1.5.0 → 1.5.1

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.
@@ -365,11 +365,17 @@ function cmdRiskScore(args) {
365
365
 
366
366
  // ── trust: trust ratchet on auto-approve ──────────────────────────────────
367
367
 
368
- function trustFile(product) { return path.join(productDir(product), 'trust.json'); }
368
+ // `namespace` (optional) routes to trust.{namespace}.json instead of trust.json — full
369
+ // isolation for a second automation surface (e.g. mobile-automation) on the same product,
370
+ // so its gate streaks never enter the min() that decides an unrelated pipeline's eligibility.
371
+ // Omitted → identical behavior to before this flag existed (trust.json, no callers changed).
372
+ function trustFile(product, namespace) {
373
+ return path.join(productDir(product), namespace ? `trust.${namespace}.json` : 'trust.json');
374
+ }
369
375
 
370
376
  function cmdTrustRecord(args) {
371
377
  requireArgs(args, ['product', 'gate', 'result']);
372
- const file = trustFile(args.product);
378
+ const file = trustFile(args.product, args.namespace);
373
379
  const store = readJson(file, {});
374
380
  if (!store[args.gate]) store[args.gate] = { clean: 0, edited: 0, streak: 0 };
375
381
  if (args.result === 'clean') { store[args.gate].clean++; store[args.gate].streak++; }
@@ -380,7 +386,7 @@ function cmdTrustRecord(args) {
380
386
 
381
387
  function cmdTrustStatus(args) {
382
388
  requireArgs(args, ['product']);
383
- const store = readJson(trustFile(args.product), {});
389
+ const store = readJson(trustFile(args.product, args.namespace), {});
384
390
  const gates = Object.keys(store);
385
391
  if (!gates.length) return console.log('NOT_ELIGIBLE — no gate history yet for this product.');
386
392
  const minStreak = Math.min(...gates.map((g) => store[g].streak || 0));
@@ -500,9 +506,12 @@ function escapeHtml(s) {
500
506
  return String(s).replace(/[&<>"']/g, (c) => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }[c]));
501
507
  }
502
508
 
503
- function cmdDashboard() {
509
+ function cmdDashboard(args) {
504
510
  const products = listProductFolders();
505
- if (!products.length) return console.log('NO_PRODUCTS — no product has run yet.');
511
+ if (!products.length) {
512
+ if (args && args.json) return console.log(JSON.stringify({ products: 0, totalRuns: 0, totalOpenBugs: 0, repos: [] }));
513
+ return console.log('NO_PRODUCTS — no product has run yet.');
514
+ }
506
515
  const stats = products.map(collectProductStats).sort((a, b) => b.runCount - a.runCount);
507
516
  const totalRuns = stats.reduce((a, s) => a + s.runCount, 0);
508
517
  const totalOpenBugs = stats.reduce((a, s) => a + s.openBugs, 0);
@@ -553,6 +562,21 @@ function cmdDashboard() {
553
562
 
554
563
  ensureDir(OUTPUTS_DIR);
555
564
  fs.writeFileSync(path.join(OUTPUTS_DIR, 'dashboard.html'), html);
565
+
566
+ if (args && args.json) {
567
+ console.log(JSON.stringify({
568
+ products: products.length,
569
+ totalRuns,
570
+ totalOpenBugs,
571
+ htmlPath: 'outputs/dashboard.html',
572
+ repos: stats.map((s) => ({
573
+ product: s.product, name: s.name, env: s.env, runCount: s.runCount,
574
+ avgReuse: s.avgReuse, flowCount: s.flowCount, openBugs: s.openBugs, bugCount: s.bugCount,
575
+ lastRunDate: s.lastRun ? s.lastRun['Date'] : null, lastRunCard: s.lastRun ? s.lastRun['Card'] : null,
576
+ })),
577
+ }));
578
+ return;
579
+ }
556
580
  console.log(`Dashboard saved: outputs/dashboard.html (${products.length} products, ${totalRuns} runs, ${totalOpenBugs} open bugs)`);
557
581
  }
558
582
 
@@ -569,11 +593,15 @@ function withinDays(dateStr, days) {
569
593
  function cmdDigest(args) {
570
594
  const days = parseInt(args.days, 10) || 7;
571
595
  const products = listProductFolders();
572
- if (!products.length) return console.log('NO_PRODUCTS');
596
+ if (!products.length) {
597
+ if (args && args.json) return console.log(JSON.stringify({ days, totalRuns: 0, totalBugs: 0, activeProducts: 0, totalProducts: 0, products: [] }));
598
+ return console.log('NO_PRODUCTS');
599
+ }
573
600
 
574
601
  let totalRuns = 0;
575
602
  let totalBugs = 0;
576
603
  const sections = [];
604
+ const productsOut = [];
577
605
  for (const product of products) {
578
606
  const info = getProductInfo(product);
579
607
  const runs = getTable(product, 'Runs Log').rows.filter((r) => withinDays(r['Date'], days));
@@ -582,10 +610,12 @@ function cmdDigest(args) {
582
610
  const bugsFiled = runs.reduce((sum, r) => sum + bugsFiledCount(r), 0);
583
611
  totalBugs += bugsFiled;
584
612
  const avgReuse = avg(pctList(runs, 'Reuse %'));
613
+ const cards = runs.map((r) => r['Card']);
585
614
  sections.push(
586
- `### ${info.name || product}\n- Runs: ${runs.length} (${runs.map((r) => r['Card']).join(', ')})\n` +
615
+ `### ${info.name || product}\n- Runs: ${runs.length} (${cards.join(', ')})\n` +
587
616
  `- Bugs filed: ${bugsFiled}\n- Avg reuse: ${avgReuse !== null ? avgReuse + '%' : 'n/a'}\n`
588
617
  );
618
+ productsOut.push({ product, name: info.name || product, runCount: runs.length, cards, bugsFiled, avgReuse });
589
619
  }
590
620
 
591
621
  const today = todayStr();
@@ -598,6 +628,16 @@ function cmdDigest(args) {
598
628
  ensureDir(OUTPUTS_DIR);
599
629
  const file = path.join(OUTPUTS_DIR, `qa-weekly-digest-${today}.md`);
600
630
  fs.writeFileSync(file, md);
631
+
632
+ if (args && args.json) {
633
+ console.log(JSON.stringify({
634
+ days, totalRuns, totalBugs,
635
+ activeProducts: sections.length, totalProducts: products.length,
636
+ mdPath: `outputs/qa-weekly-digest-${today}.md`,
637
+ products: productsOut,
638
+ }));
639
+ return;
640
+ }
601
641
  console.log(
602
642
  `Digest saved: outputs/qa-weekly-digest-${today}.md — ${totalRuns} runs, ${totalBugs} bugs across ` +
603
643
  `${sections.length} active product(s) in the last ${days} days.`
@@ -606,9 +646,12 @@ function cmdDigest(args) {
606
646
 
607
647
  // ── roi: token-spend / ROI view ────────────────────────────────────────────
608
648
 
609
- function cmdRoi() {
649
+ function cmdRoi(args) {
610
650
  const products = listProductFolders();
611
- if (!products.length) return console.log('NO_PRODUCTS');
651
+ if (!products.length) {
652
+ if (args && args.json) return console.log(JSON.stringify({ totalRuns: 0, totalBugs: 0, hasTracking: false, rows: [] }));
653
+ return console.log('NO_PRODUCTS');
654
+ }
612
655
 
613
656
  const tracking = tryReadTrackingLog();
614
657
  const hasTracking = Array.isArray(tracking) && tracking.length > 0;
@@ -660,6 +703,16 @@ function cmdRoi() {
660
703
 
661
704
  ensureDir(OUTPUTS_DIR);
662
705
  fs.writeFileSync(path.join(OUTPUTS_DIR, 'roi-report.md'), md);
706
+
707
+ if (args && args.json) {
708
+ console.log(JSON.stringify({
709
+ totalRuns, totalBugs, hasTracking,
710
+ avgTokensPerRun: hasTracking && tokenRuns ? Math.round(totalTokens / tokenRuns) : null,
711
+ mdPath: 'outputs/roi-report.md',
712
+ rows: rowsOut,
713
+ }));
714
+ return;
715
+ }
663
716
  console.log(
664
717
  `ROI report saved: outputs/roi-report.md — ${totalRuns} runs, ${totalBugs} bugs` +
665
718
  `${hasTracking ? ', token data included' : ' (token data unavailable — set QA_TRACKING_DIR to enable)'}.`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@roopesh.yadava/qa-pack",
3
- "version": "1.5.0",
3
+ "version": "1.5.1",
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"