qa-engineer 0.9.0 → 0.9.2

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 (52) hide show
  1. package/COMPATIBILITY.md +25 -15
  2. package/LICENSE +1 -1
  3. package/README.md +22 -4
  4. package/package.json +4 -3
  5. package/packages/installer/README.md +1 -1
  6. package/packages/installer/lib/agents/registry.mjs +46 -0
  7. package/packages/installer/lib/commands/doctor.mjs +6 -6
  8. package/packages/installer/lib/commands/install.mjs +32 -5
  9. package/packages/installer/lib/commands/onboard.mjs +2 -2
  10. package/packages/installer/lib/core/config.mjs +1 -1
  11. package/packages/installer/lib/core/lockfile.mjs +1 -1
  12. package/packages/installer/lib/core/manifest.mjs +3 -0
  13. package/packages/installer/lib/core/wrappers.mjs +4 -4
  14. package/packages/installer/lib/ui/theme.mjs +1 -1
  15. package/packages/installer/package.json +1 -1
  16. package/packages/installer/schemas/qa-lock.schema.json +1 -1
  17. package/packages/installer/schemas/qa.config.schema.json +1 -1
  18. package/shared/analysis/lib/qa_analysis/branding.json +1 -1
  19. package/shared/analysis/lib/qa_analysis/cli.py +15 -0
  20. package/shared/analysis/lib/qa_analysis/har.py +32 -3
  21. package/shared/analysis/lib/qa_analysis/junit.py +25 -1
  22. package/shared/analysis/lib/qa_analysis/redaction.py +10 -2
  23. package/shared/analysis/lib/qa_analysis/report_html.py +781 -0
  24. package/skills/qa/README.md +1 -1
  25. package/skills/qa/SKILL.md +1 -1
  26. package/skills/qa-api/references/deterministic-tooling.md +2 -0
  27. package/skills/qa-api/references/evidence-and-reporting.md +29 -4
  28. package/skills/qa-audit/references/deterministic-tooling.md +2 -0
  29. package/skills/qa-audit/references/evidence-and-reporting.md +29 -4
  30. package/skills/qa-debug/references/deterministic-tooling.md +2 -0
  31. package/skills/qa-debug/references/evidence-and-reporting.md +29 -4
  32. package/skills/qa-example/SKILL.md +1 -1
  33. package/skills/qa-example/contracts/self-check-report.schema.json +1 -1
  34. package/skills/qa-explore/SKILL.md +22 -5
  35. package/skills/qa-explore/contracts/explore-result.schema.json +26 -2
  36. package/skills/qa-explore/references/deterministic-tooling.md +130 -0
  37. package/skills/qa-explore/references/evidence-and-reporting.md +29 -4
  38. package/skills/qa-explore/references/report-pipeline.md +89 -8
  39. package/skills/qa-fix/references/deterministic-tooling.md +2 -0
  40. package/skills/qa-fix/references/evidence-and-reporting.md +29 -4
  41. package/skills/qa-flaky/references/deterministic-tooling.md +2 -0
  42. package/skills/qa-flaky/references/evidence-and-reporting.md +29 -4
  43. package/skills/qa-generate/references/evidence-and-reporting.md +29 -4
  44. package/skills/qa-init/references/deterministic-tooling.md +2 -0
  45. package/skills/qa-init/references/evidence-and-reporting.md +29 -4
  46. package/skills/qa-report/SKILL.md +3 -2
  47. package/skills/qa-report/references/deterministic-tooling.md +2 -0
  48. package/skills/qa-report/references/evidence-and-reporting.md +29 -4
  49. package/skills/qa-review/references/evidence-and-reporting.md +29 -4
  50. package/skills/qa-run/references/deterministic-tooling.md +2 -0
  51. package/skills/qa-run/references/evidence-and-reporting.md +29 -4
  52. package/packages/installer/lib/core/schema-validate.mjs +0 -142
@@ -24,6 +24,30 @@ def _int(value, default=0):
24
24
  return default
25
25
 
26
26
 
27
+ def _duration_ms(raw, path):
28
+ """`time` in seconds as whole milliseconds.
29
+
30
+ An absent or empty attribute is zero — plenty of runners omit it. A value
31
+ that is present but not a finite number means the document is not what it
32
+ claims to be, so it raises: found by the Node port's parity corpus, where
33
+ `time="not-a-number"` escaped as a bare ValueError and `time="nan"` as
34
+ "cannot convert float NaN to integer". Both reached the caller as a traceback
35
+ and exit 1, while the CLI's documented failure mode is exit 2 with
36
+ {error, detail}.
37
+ """
38
+ if raw is None or raw == "":
39
+ return 0
40
+ try:
41
+ seconds = float(raw)
42
+ except (TypeError, ValueError):
43
+ raise MalformedArtifact(
44
+ f"testcase time={raw!r} is not a number at {path}"
45
+ ) from None
46
+ if seconds != seconds or seconds in (float("inf"), float("-inf")):
47
+ raise MalformedArtifact(f"testcase time={raw!r} is not a finite number at {path}")
48
+ return int(round(seconds * 1000))
49
+
50
+
27
51
  def parse_junit(path):
28
52
  """Parse a JUnit XML file into a normalized result.
29
53
 
@@ -65,7 +89,7 @@ def parse_junit(path):
65
89
  "title": case.get("name", ""),
66
90
  "file": case.get("classname", ""),
67
91
  "status": status,
68
- "durationMs": int(round(float(case.get("time", "0") or "0") * 1000)),
92
+ "durationMs": _duration_ms(case.get("time"), path),
69
93
  }
70
94
  if message:
71
95
  entry["message"] = message
@@ -18,8 +18,16 @@ _RULES = [
18
18
  ("openai-key", re.compile(r"sk-[A-Za-z0-9]{20,}")),
19
19
  ("bearer", re.compile(r"(?i)\bBearer\s+[A-Za-z0-9._~+/-]+=*")),
20
20
  # Sensitive header lines: keep the header name, mask the value.
21
- ("auth-header", re.compile(r"(?im)^(\s*(?:authorization|proxy-authorization)\s*[:=]\s*).+$")),
22
- ("cookie-header", re.compile(r"(?im)^(\s*(?:set-cookie|cookie)\s*[:=]\s*).+$")),
21
+ #
22
+ # The value is `[^\r\n]+` rather than `.+$`, and the indent is `[ \t]*` rather
23
+ # than `\s*`, for one reason: on CRLF text, `.+` swallows the carriage return
24
+ # and `$` matches before the newline, so redacting a header silently rewrote
25
+ # the line ending to LF and corrupted the rest of the document's endings.
26
+ # Horizontal whitespace is also what a header indent actually is. Found by the
27
+ # Node port's parity corpus, where the two languages disagreed here and
28
+ # JavaScript was right.
29
+ ("auth-header", re.compile(r"(?im)^([ \t]*(?:authorization|proxy-authorization)[ \t]*[:=][ \t]*)[^\r\n]+")),
30
+ ("cookie-header", re.compile(r"(?im)^([ \t]*(?:set-cookie|cookie)[ \t]*[:=][ \t]*)[^\r\n]+")),
23
31
  # Secret-like assignments: key=value / "key": "value".
24
32
  ("assigned-secret", re.compile(
25
33
  r'(?i)(\b(?:password|passwd|pwd|secret|token|api[_-]?key|access[_-]?key|client[_-]?secret)\b\s*[:=]\s*["\']?)'