qa-engineer 0.9.1 → 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 (39) hide show
  1. package/COMPATIBILITY.md +25 -15
  2. package/README.md +20 -2
  3. package/package.json +4 -3
  4. package/packages/installer/lib/agents/registry.mjs +46 -0
  5. package/packages/installer/lib/commands/install.mjs +31 -4
  6. package/packages/installer/lib/core/config.mjs +1 -1
  7. package/packages/installer/lib/core/lockfile.mjs +1 -1
  8. package/packages/installer/lib/core/manifest.mjs +3 -0
  9. package/packages/installer/package.json +1 -1
  10. package/shared/analysis/lib/qa_analysis/cli.py +15 -0
  11. package/shared/analysis/lib/qa_analysis/har.py +32 -3
  12. package/shared/analysis/lib/qa_analysis/junit.py +25 -1
  13. package/shared/analysis/lib/qa_analysis/redaction.py +10 -2
  14. package/shared/analysis/lib/qa_analysis/report_html.py +781 -0
  15. package/skills/qa-api/references/deterministic-tooling.md +2 -0
  16. package/skills/qa-api/references/evidence-and-reporting.md +29 -4
  17. package/skills/qa-audit/references/deterministic-tooling.md +2 -0
  18. package/skills/qa-audit/references/evidence-and-reporting.md +29 -4
  19. package/skills/qa-debug/references/deterministic-tooling.md +2 -0
  20. package/skills/qa-debug/references/evidence-and-reporting.md +29 -4
  21. package/skills/qa-explore/SKILL.md +22 -5
  22. package/skills/qa-explore/contracts/explore-result.schema.json +26 -2
  23. package/skills/qa-explore/references/deterministic-tooling.md +130 -0
  24. package/skills/qa-explore/references/evidence-and-reporting.md +29 -4
  25. package/skills/qa-explore/references/report-pipeline.md +89 -8
  26. package/skills/qa-fix/references/deterministic-tooling.md +2 -0
  27. package/skills/qa-fix/references/evidence-and-reporting.md +29 -4
  28. package/skills/qa-flaky/references/deterministic-tooling.md +2 -0
  29. package/skills/qa-flaky/references/evidence-and-reporting.md +29 -4
  30. package/skills/qa-generate/references/evidence-and-reporting.md +29 -4
  31. package/skills/qa-init/references/deterministic-tooling.md +2 -0
  32. package/skills/qa-init/references/evidence-and-reporting.md +29 -4
  33. package/skills/qa-report/SKILL.md +3 -2
  34. package/skills/qa-report/references/deterministic-tooling.md +2 -0
  35. package/skills/qa-report/references/evidence-and-reporting.md +29 -4
  36. package/skills/qa-review/references/evidence-and-reporting.md +29 -4
  37. package/skills/qa-run/references/deterministic-tooling.md +2 -0
  38. package/skills/qa-run/references/evidence-and-reporting.md +29 -4
  39. package/packages/installer/lib/core/schema-validate.mjs +0 -142
@@ -61,6 +61,8 @@ Framework-agnostic parsing, redaction, and validation.
61
61
  | `validate` | `python3 <skill-dir>/scripts/qa_tool.py analysis validate <instance.json> <schema.json>` | `{valid: bool, errors: [...]}`; exit 1 when invalid |
62
62
  | `classify` | `python3 <skill-dir>/scripts/qa_tool.py analysis classify "<error message>" [--http-status N]` | `{classification, confidence, reason}` from the shared taxonomy |
63
63
  | `context` | `python3 <skill-dir>/scripts/qa_tool.py analysis context [--root DIR] [--path .qa/context.md]` | The parsed, schema-validated project context as JSON |
64
+ | `report-html` | `python3 <skill-dir>/scripts/qa_tool.py analysis report-html <result.json> [--out report.html]` | The result rendered as one self-contained HTML report — every required field, footer included. Run it instead of writing HTML |
65
+ | `branding` | `python3 <skill-dir>/scripts/qa_tool.py analysis branding --format markdown\|html\|text` | The exact attribution footer bytes for a **rendered** report (never for a JSON artifact) |
64
66
 
65
67
  ## 3. Diagnostic engine — `qa_tool.py diagnostics`
66
68
 
@@ -27,17 +27,42 @@ Any skill that reaches a conclusion — a detected fact, a chosen strategy, a cl
27
27
  | Confidence | Calibrated per rule 4; omitted rather than invented when the skill did not weigh alternatives |
28
28
  | Recommendations | The next action, named concretely — often the next command and the artifact to feed it |
29
29
 
30
+ ## Rendering the HTML report
31
+
32
+ **Never type the HTML.** When a skill's result conforms to a contract the renderer
33
+ supports, the HTML report is generated from that artifact:
34
+
35
+ ```bash
36
+ python3 <SKILL_DIR>/scripts/qa_tool.py analysis report-html <result.json> --out <report.html>
37
+ ```
38
+
39
+ The reason is not tidiness. A hand-written report is a second, lossy copy of the
40
+ artifact, and it loses exactly what the reader needs: the first live `/qa-explore`
41
+ run wrote findings whose `actual`, `expected`, and `fixDirection` were all required,
42
+ all present in the JSON, and all absent from the page — collapsed into one sentence
43
+ that left the reader to guess what correct behaviour would have been. The renderer
44
+ cannot make that mistake, because the fields are in its template.
45
+
46
+ What the renderer guarantees per finding: severity, the defect, **what happens
47
+ now**, **what should happen instead**, how to reproduce it, the fix direction, and
48
+ every evidence entry — plus the attribution footer, in one self-contained file with
49
+ no external assets.
50
+
51
+ Run `qa_tool.py analysis report-html --help` for the supported contracts. For an
52
+ artifact it does not support, write the HTML by hand from the contract's fields —
53
+ and render **every** field a reader needs, in that order.
54
+
30
55
  ## Attribution on rendered reports
31
56
 
32
57
  A report a person opens carries a product attribution footer, the way a Lighthouse
33
58
  or Allure report does. It is rendered, never typed: the exact bytes come from the
34
59
  bundled analysis toolkit, so every report is identical and a wording change is a
35
- one-file edit.
60
+ one-file edit. `report-html` already embeds it; for any other rendering, ask for it:
36
61
 
37
62
  ```bash
38
- PYTHONPATH="$QA_LIB" python3 -m qa_analysis.cli branding --format html
39
- PYTHONPATH="$QA_LIB" python3 -m qa_analysis.cli branding --format markdown
40
- PYTHONPATH="$QA_LIB" python3 -m qa_analysis.cli branding --format text
63
+ python3 <SKILL_DIR>/scripts/qa_tool.py analysis branding --format markdown
64
+ python3 <SKILL_DIR>/scripts/qa_tool.py analysis branding --format html
65
+ python3 <SKILL_DIR>/scripts/qa_tool.py analysis branding --format text
41
66
  ```
42
67
 
43
68
  Append the output as the last element of the rendered document: `html` inside
@@ -27,17 +27,42 @@ Any skill that reaches a conclusion — a detected fact, a chosen strategy, a cl
27
27
  | Confidence | Calibrated per rule 4; omitted rather than invented when the skill did not weigh alternatives |
28
28
  | Recommendations | The next action, named concretely — often the next command and the artifact to feed it |
29
29
 
30
+ ## Rendering the HTML report
31
+
32
+ **Never type the HTML.** When a skill's result conforms to a contract the renderer
33
+ supports, the HTML report is generated from that artifact:
34
+
35
+ ```bash
36
+ python3 <SKILL_DIR>/scripts/qa_tool.py analysis report-html <result.json> --out <report.html>
37
+ ```
38
+
39
+ The reason is not tidiness. A hand-written report is a second, lossy copy of the
40
+ artifact, and it loses exactly what the reader needs: the first live `/qa-explore`
41
+ run wrote findings whose `actual`, `expected`, and `fixDirection` were all required,
42
+ all present in the JSON, and all absent from the page — collapsed into one sentence
43
+ that left the reader to guess what correct behaviour would have been. The renderer
44
+ cannot make that mistake, because the fields are in its template.
45
+
46
+ What the renderer guarantees per finding: severity, the defect, **what happens
47
+ now**, **what should happen instead**, how to reproduce it, the fix direction, and
48
+ every evidence entry — plus the attribution footer, in one self-contained file with
49
+ no external assets.
50
+
51
+ Run `qa_tool.py analysis report-html --help` for the supported contracts. For an
52
+ artifact it does not support, write the HTML by hand from the contract's fields —
53
+ and render **every** field a reader needs, in that order.
54
+
30
55
  ## Attribution on rendered reports
31
56
 
32
57
  A report a person opens carries a product attribution footer, the way a Lighthouse
33
58
  or Allure report does. It is rendered, never typed: the exact bytes come from the
34
59
  bundled analysis toolkit, so every report is identical and a wording change is a
35
- one-file edit.
60
+ one-file edit. `report-html` already embeds it; for any other rendering, ask for it:
36
61
 
37
62
  ```bash
38
- PYTHONPATH="$QA_LIB" python3 -m qa_analysis.cli branding --format html
39
- PYTHONPATH="$QA_LIB" python3 -m qa_analysis.cli branding --format markdown
40
- PYTHONPATH="$QA_LIB" python3 -m qa_analysis.cli branding --format text
63
+ python3 <SKILL_DIR>/scripts/qa_tool.py analysis branding --format markdown
64
+ python3 <SKILL_DIR>/scripts/qa_tool.py analysis branding --format html
65
+ python3 <SKILL_DIR>/scripts/qa_tool.py analysis branding --format text
41
66
  ```
42
67
 
43
68
  Append the output as the last element of the rendered document: `html` inside
@@ -61,6 +61,8 @@ Framework-agnostic parsing, redaction, and validation.
61
61
  | `validate` | `python3 <skill-dir>/scripts/qa_tool.py analysis validate <instance.json> <schema.json>` | `{valid: bool, errors: [...]}`; exit 1 when invalid |
62
62
  | `classify` | `python3 <skill-dir>/scripts/qa_tool.py analysis classify "<error message>" [--http-status N]` | `{classification, confidence, reason}` from the shared taxonomy |
63
63
  | `context` | `python3 <skill-dir>/scripts/qa_tool.py analysis context [--root DIR] [--path .qa/context.md]` | The parsed, schema-validated project context as JSON |
64
+ | `report-html` | `python3 <skill-dir>/scripts/qa_tool.py analysis report-html <result.json> [--out report.html]` | The result rendered as one self-contained HTML report — every required field, footer included. Run it instead of writing HTML |
65
+ | `branding` | `python3 <skill-dir>/scripts/qa_tool.py analysis branding --format markdown\|html\|text` | The exact attribution footer bytes for a **rendered** report (never for a JSON artifact) |
64
66
 
65
67
  ## 3. Diagnostic engine — `qa_tool.py diagnostics`
66
68
 
@@ -27,17 +27,42 @@ Any skill that reaches a conclusion — a detected fact, a chosen strategy, a cl
27
27
  | Confidence | Calibrated per rule 4; omitted rather than invented when the skill did not weigh alternatives |
28
28
  | Recommendations | The next action, named concretely — often the next command and the artifact to feed it |
29
29
 
30
+ ## Rendering the HTML report
31
+
32
+ **Never type the HTML.** When a skill's result conforms to a contract the renderer
33
+ supports, the HTML report is generated from that artifact:
34
+
35
+ ```bash
36
+ python3 <SKILL_DIR>/scripts/qa_tool.py analysis report-html <result.json> --out <report.html>
37
+ ```
38
+
39
+ The reason is not tidiness. A hand-written report is a second, lossy copy of the
40
+ artifact, and it loses exactly what the reader needs: the first live `/qa-explore`
41
+ run wrote findings whose `actual`, `expected`, and `fixDirection` were all required,
42
+ all present in the JSON, and all absent from the page — collapsed into one sentence
43
+ that left the reader to guess what correct behaviour would have been. The renderer
44
+ cannot make that mistake, because the fields are in its template.
45
+
46
+ What the renderer guarantees per finding: severity, the defect, **what happens
47
+ now**, **what should happen instead**, how to reproduce it, the fix direction, and
48
+ every evidence entry — plus the attribution footer, in one self-contained file with
49
+ no external assets.
50
+
51
+ Run `qa_tool.py analysis report-html --help` for the supported contracts. For an
52
+ artifact it does not support, write the HTML by hand from the contract's fields —
53
+ and render **every** field a reader needs, in that order.
54
+
30
55
  ## Attribution on rendered reports
31
56
 
32
57
  A report a person opens carries a product attribution footer, the way a Lighthouse
33
58
  or Allure report does. It is rendered, never typed: the exact bytes come from the
34
59
  bundled analysis toolkit, so every report is identical and a wording change is a
35
- one-file edit.
60
+ one-file edit. `report-html` already embeds it; for any other rendering, ask for it:
36
61
 
37
62
  ```bash
38
- PYTHONPATH="$QA_LIB" python3 -m qa_analysis.cli branding --format html
39
- PYTHONPATH="$QA_LIB" python3 -m qa_analysis.cli branding --format markdown
40
- PYTHONPATH="$QA_LIB" python3 -m qa_analysis.cli branding --format text
63
+ python3 <SKILL_DIR>/scripts/qa_tool.py analysis branding --format markdown
64
+ python3 <SKILL_DIR>/scripts/qa_tool.py analysis branding --format html
65
+ python3 <SKILL_DIR>/scripts/qa_tool.py analysis branding --format text
41
66
  ```
42
67
 
43
68
  Append the output as the last element of the rendered document: `html` inside
@@ -41,7 +41,7 @@ Do not investigate failures here (that is `/qa-debug`) or plan repairs (`/qa-fix
41
41
  2. **Aggregate deterministically.** Run the engine's summarize step (see Tooling) to compute the test totals, the by-classification breakdown, the ranked findings and recommendations, and the release-readiness verdict.
42
42
  3. **Frame for audiences.** Write the executive summary (shippability in a paragraph and a verdict) and the engineering summary (what broke, why, who owns it, in priority order) from the same aggregated data.
43
43
  4. **Detail.** Fill the test, failure, coverage, and risk sections; mark coverage unavailable rather than inventing it when there is no coverage data.
44
- 5. **Render.** Produce the report in Markdown, an HTML-ready structure, and the JSON result the same content in three forms. Append the rendered attribution footer (see Tooling) to the Markdown and HTML renderings only; the JSON result is an interface and carries no footer.
44
+ 5. **Render.** Write the JSON result first and validate it; the Markdown and HTML are renderings of it, not parallel drafts. Render the HTML with the report renderer (see Tooling) rather than typing it. Append the rendered attribution footer to the Markdown; the renderer embeds it in the HTML, and the JSON result is an interface and carries no footer.
45
45
  6. **Report.** Emit the report result and present the Markdown.
46
46
 
47
47
  ## Guardrails
@@ -61,7 +61,8 @@ Invoke the bundled engine through its launcher, as documented in [references/det
61
61
  | Report aggregator | `python3 <SKILL_DIR>/scripts/qa_tool.py diagnostics summarize --execution-result <path> --diagnosis <path>` | Totals, by-classification breakdown, top-priority findings, release-readiness verdict | Aggregate the structured results manually per the report-aggregation module and mark the report degraded |
62
62
  | One-shot pipeline | `python3 <SKILL_DIR>/scripts/qa_tool.py diagnostics report --execution-result <path>` | Diagnosis, plans, and summary in a single call when no diagnosis exists yet | Run `diagnose` then `summarize` separately |
63
63
  | Contract self-check | `python3 <SKILL_DIR>/scripts/qa_tool.py analysis validate <report.json> <schema.json>` | `{valid, errors}` before the report is declared complete | None: an unvalidated report is not complete |
64
- | Attribution footer | `python3 <SKILL_DIR>/scripts/qa_tool.py analysis branding --format markdown` (or `html`) | The exact footer bytes to append to the **rendered** report | Omit the footer; never retype it |
64
+ | HTML report renderer | `python3 <SKILL_DIR>/scripts/qa_tool.py analysis report-html <report-result.json> --out report.html` | The self-contained HTML rendering of the validated result, footer included | Write the HTML by hand from the contract fields and say it was not machine-rendered |
65
+ | Attribution footer | `python3 <SKILL_DIR>/scripts/qa_tool.py analysis branding --format markdown` | The exact footer bytes to append to the **Markdown** rendering (`report-html` already embeds it) | Omit the footer; never retype it |
65
66
 
66
67
  A missing `qa_tool.py` means the engine is not installed.
67
68
 
@@ -61,6 +61,8 @@ Framework-agnostic parsing, redaction, and validation.
61
61
  | `validate` | `python3 <skill-dir>/scripts/qa_tool.py analysis validate <instance.json> <schema.json>` | `{valid: bool, errors: [...]}`; exit 1 when invalid |
62
62
  | `classify` | `python3 <skill-dir>/scripts/qa_tool.py analysis classify "<error message>" [--http-status N]` | `{classification, confidence, reason}` from the shared taxonomy |
63
63
  | `context` | `python3 <skill-dir>/scripts/qa_tool.py analysis context [--root DIR] [--path .qa/context.md]` | The parsed, schema-validated project context as JSON |
64
+ | `report-html` | `python3 <skill-dir>/scripts/qa_tool.py analysis report-html <result.json> [--out report.html]` | The result rendered as one self-contained HTML report — every required field, footer included. Run it instead of writing HTML |
65
+ | `branding` | `python3 <skill-dir>/scripts/qa_tool.py analysis branding --format markdown\|html\|text` | The exact attribution footer bytes for a **rendered** report (never for a JSON artifact) |
64
66
 
65
67
  ## 3. Diagnostic engine — `qa_tool.py diagnostics`
66
68
 
@@ -27,17 +27,42 @@ Any skill that reaches a conclusion — a detected fact, a chosen strategy, a cl
27
27
  | Confidence | Calibrated per rule 4; omitted rather than invented when the skill did not weigh alternatives |
28
28
  | Recommendations | The next action, named concretely — often the next command and the artifact to feed it |
29
29
 
30
+ ## Rendering the HTML report
31
+
32
+ **Never type the HTML.** When a skill's result conforms to a contract the renderer
33
+ supports, the HTML report is generated from that artifact:
34
+
35
+ ```bash
36
+ python3 <SKILL_DIR>/scripts/qa_tool.py analysis report-html <result.json> --out <report.html>
37
+ ```
38
+
39
+ The reason is not tidiness. A hand-written report is a second, lossy copy of the
40
+ artifact, and it loses exactly what the reader needs: the first live `/qa-explore`
41
+ run wrote findings whose `actual`, `expected`, and `fixDirection` were all required,
42
+ all present in the JSON, and all absent from the page — collapsed into one sentence
43
+ that left the reader to guess what correct behaviour would have been. The renderer
44
+ cannot make that mistake, because the fields are in its template.
45
+
46
+ What the renderer guarantees per finding: severity, the defect, **what happens
47
+ now**, **what should happen instead**, how to reproduce it, the fix direction, and
48
+ every evidence entry — plus the attribution footer, in one self-contained file with
49
+ no external assets.
50
+
51
+ Run `qa_tool.py analysis report-html --help` for the supported contracts. For an
52
+ artifact it does not support, write the HTML by hand from the contract's fields —
53
+ and render **every** field a reader needs, in that order.
54
+
30
55
  ## Attribution on rendered reports
31
56
 
32
57
  A report a person opens carries a product attribution footer, the way a Lighthouse
33
58
  or Allure report does. It is rendered, never typed: the exact bytes come from the
34
59
  bundled analysis toolkit, so every report is identical and a wording change is a
35
- one-file edit.
60
+ one-file edit. `report-html` already embeds it; for any other rendering, ask for it:
36
61
 
37
62
  ```bash
38
- PYTHONPATH="$QA_LIB" python3 -m qa_analysis.cli branding --format html
39
- PYTHONPATH="$QA_LIB" python3 -m qa_analysis.cli branding --format markdown
40
- PYTHONPATH="$QA_LIB" python3 -m qa_analysis.cli branding --format text
63
+ python3 <SKILL_DIR>/scripts/qa_tool.py analysis branding --format markdown
64
+ python3 <SKILL_DIR>/scripts/qa_tool.py analysis branding --format html
65
+ python3 <SKILL_DIR>/scripts/qa_tool.py analysis branding --format text
41
66
  ```
42
67
 
43
68
  Append the output as the last element of the rendered document: `html` inside
@@ -27,17 +27,42 @@ Any skill that reaches a conclusion — a detected fact, a chosen strategy, a cl
27
27
  | Confidence | Calibrated per rule 4; omitted rather than invented when the skill did not weigh alternatives |
28
28
  | Recommendations | The next action, named concretely — often the next command and the artifact to feed it |
29
29
 
30
+ ## Rendering the HTML report
31
+
32
+ **Never type the HTML.** When a skill's result conforms to a contract the renderer
33
+ supports, the HTML report is generated from that artifact:
34
+
35
+ ```bash
36
+ python3 <SKILL_DIR>/scripts/qa_tool.py analysis report-html <result.json> --out <report.html>
37
+ ```
38
+
39
+ The reason is not tidiness. A hand-written report is a second, lossy copy of the
40
+ artifact, and it loses exactly what the reader needs: the first live `/qa-explore`
41
+ run wrote findings whose `actual`, `expected`, and `fixDirection` were all required,
42
+ all present in the JSON, and all absent from the page — collapsed into one sentence
43
+ that left the reader to guess what correct behaviour would have been. The renderer
44
+ cannot make that mistake, because the fields are in its template.
45
+
46
+ What the renderer guarantees per finding: severity, the defect, **what happens
47
+ now**, **what should happen instead**, how to reproduce it, the fix direction, and
48
+ every evidence entry — plus the attribution footer, in one self-contained file with
49
+ no external assets.
50
+
51
+ Run `qa_tool.py analysis report-html --help` for the supported contracts. For an
52
+ artifact it does not support, write the HTML by hand from the contract's fields —
53
+ and render **every** field a reader needs, in that order.
54
+
30
55
  ## Attribution on rendered reports
31
56
 
32
57
  A report a person opens carries a product attribution footer, the way a Lighthouse
33
58
  or Allure report does. It is rendered, never typed: the exact bytes come from the
34
59
  bundled analysis toolkit, so every report is identical and a wording change is a
35
- one-file edit.
60
+ one-file edit. `report-html` already embeds it; for any other rendering, ask for it:
36
61
 
37
62
  ```bash
38
- PYTHONPATH="$QA_LIB" python3 -m qa_analysis.cli branding --format html
39
- PYTHONPATH="$QA_LIB" python3 -m qa_analysis.cli branding --format markdown
40
- PYTHONPATH="$QA_LIB" python3 -m qa_analysis.cli branding --format text
63
+ python3 <SKILL_DIR>/scripts/qa_tool.py analysis branding --format markdown
64
+ python3 <SKILL_DIR>/scripts/qa_tool.py analysis branding --format html
65
+ python3 <SKILL_DIR>/scripts/qa_tool.py analysis branding --format text
41
66
  ```
42
67
 
43
68
  Append the output as the last element of the rendered document: `html` inside
@@ -61,6 +61,8 @@ Framework-agnostic parsing, redaction, and validation.
61
61
  | `validate` | `python3 <skill-dir>/scripts/qa_tool.py analysis validate <instance.json> <schema.json>` | `{valid: bool, errors: [...]}`; exit 1 when invalid |
62
62
  | `classify` | `python3 <skill-dir>/scripts/qa_tool.py analysis classify "<error message>" [--http-status N]` | `{classification, confidence, reason}` from the shared taxonomy |
63
63
  | `context` | `python3 <skill-dir>/scripts/qa_tool.py analysis context [--root DIR] [--path .qa/context.md]` | The parsed, schema-validated project context as JSON |
64
+ | `report-html` | `python3 <skill-dir>/scripts/qa_tool.py analysis report-html <result.json> [--out report.html]` | The result rendered as one self-contained HTML report — every required field, footer included. Run it instead of writing HTML |
65
+ | `branding` | `python3 <skill-dir>/scripts/qa_tool.py analysis branding --format markdown\|html\|text` | The exact attribution footer bytes for a **rendered** report (never for a JSON artifact) |
64
66
 
65
67
  ## 3. Diagnostic engine — `qa_tool.py diagnostics`
66
68
 
@@ -27,17 +27,42 @@ Any skill that reaches a conclusion — a detected fact, a chosen strategy, a cl
27
27
  | Confidence | Calibrated per rule 4; omitted rather than invented when the skill did not weigh alternatives |
28
28
  | Recommendations | The next action, named concretely — often the next command and the artifact to feed it |
29
29
 
30
+ ## Rendering the HTML report
31
+
32
+ **Never type the HTML.** When a skill's result conforms to a contract the renderer
33
+ supports, the HTML report is generated from that artifact:
34
+
35
+ ```bash
36
+ python3 <SKILL_DIR>/scripts/qa_tool.py analysis report-html <result.json> --out <report.html>
37
+ ```
38
+
39
+ The reason is not tidiness. A hand-written report is a second, lossy copy of the
40
+ artifact, and it loses exactly what the reader needs: the first live `/qa-explore`
41
+ run wrote findings whose `actual`, `expected`, and `fixDirection` were all required,
42
+ all present in the JSON, and all absent from the page — collapsed into one sentence
43
+ that left the reader to guess what correct behaviour would have been. The renderer
44
+ cannot make that mistake, because the fields are in its template.
45
+
46
+ What the renderer guarantees per finding: severity, the defect, **what happens
47
+ now**, **what should happen instead**, how to reproduce it, the fix direction, and
48
+ every evidence entry — plus the attribution footer, in one self-contained file with
49
+ no external assets.
50
+
51
+ Run `qa_tool.py analysis report-html --help` for the supported contracts. For an
52
+ artifact it does not support, write the HTML by hand from the contract's fields —
53
+ and render **every** field a reader needs, in that order.
54
+
30
55
  ## Attribution on rendered reports
31
56
 
32
57
  A report a person opens carries a product attribution footer, the way a Lighthouse
33
58
  or Allure report does. It is rendered, never typed: the exact bytes come from the
34
59
  bundled analysis toolkit, so every report is identical and a wording change is a
35
- one-file edit.
60
+ one-file edit. `report-html` already embeds it; for any other rendering, ask for it:
36
61
 
37
62
  ```bash
38
- PYTHONPATH="$QA_LIB" python3 -m qa_analysis.cli branding --format html
39
- PYTHONPATH="$QA_LIB" python3 -m qa_analysis.cli branding --format markdown
40
- PYTHONPATH="$QA_LIB" python3 -m qa_analysis.cli branding --format text
63
+ python3 <SKILL_DIR>/scripts/qa_tool.py analysis branding --format markdown
64
+ python3 <SKILL_DIR>/scripts/qa_tool.py analysis branding --format html
65
+ python3 <SKILL_DIR>/scripts/qa_tool.py analysis branding --format text
41
66
  ```
42
67
 
43
68
  Append the output as the last element of the rendered document: `html` inside
@@ -1,142 +0,0 @@
1
- // A dependency-free validator for the JSON Schema subset this project uses.
2
- // It is the JavaScript twin of the Python contract validator in
3
- // shared/analysis/lib/qa_analysis/contracts.py: same supported subset, same
4
- // semantics, so a document that passes one passes the other. That promise is
5
- // tested, not asserted — tests/parity/validator-cases.json runs through both.
6
- //
7
- // Anything outside the subset is a programming error in the schema, reported
8
- // rather than silently ignored. `allOf`/`if`/`then`/`else` are in the subset
9
- // because the pack's safety invariants are cross-field implications.
10
-
11
- // Keep in sync with SUPPORTED_KEYWORDS in
12
- // shared/analysis/lib/qa_analysis/contracts.py and the table in
13
- // docs/skills/output-contracts.md (checked by scripts/check-spec-code-sync.mjs).
14
- const SUPPORTED = new Set([
15
- '$schema', '$id', 'title', 'description', 'type', 'properties', 'required',
16
- 'additionalProperties', 'items', 'enum', 'const', 'pattern', 'minimum',
17
- 'maximum', 'minItems', 'maxItems', 'minLength', 'maxLength', 'default',
18
- 'examples', 'format', 'allOf', 'if', 'then', 'else',
19
- ]);
20
-
21
- // RFC 3339 date-time — the same rule as the Python twin.
22
- const DATE_TIME = /^\d{4}-\d{2}-\d{2}[Tt]\d{2}:\d{2}:\d{2}(\.\d+)?([Zz]|[+-]\d{2}:\d{2})$/;
23
-
24
- function typeOf(value) {
25
- if (value === null) return 'null';
26
- if (Array.isArray(value)) return 'array';
27
- if (Number.isInteger(value)) return 'integer';
28
- return typeof value; // string | number | boolean | object
29
- }
30
-
31
- function matchesType(value, type) {
32
- if (type === 'integer') return Number.isInteger(value) && typeof value !== 'boolean';
33
- if (type === 'number') return typeof value === 'number';
34
- if (type === 'object') return value !== null && typeof value === 'object' && !Array.isArray(value);
35
- if (type === 'array') return Array.isArray(value);
36
- return typeOf(value) === type;
37
- }
38
-
39
- /** True when value satisfies schema; used for if/then branch selection. */
40
- function satisfies(value, schema) {
41
- const probe = [];
42
- validateNode(value, schema, '', probe);
43
- return probe.length === 0;
44
- }
45
-
46
- function validateNode(value, schema, pointer, errors) {
47
- for (const key of Object.keys(schema)) {
48
- if (!SUPPORTED.has(key)) {
49
- errors.push(`${pointer || '/'}: schema uses unsupported keyword "${key}"`);
50
- }
51
- }
52
-
53
- if (schema.type) {
54
- const types = Array.isArray(schema.type) ? schema.type : [schema.type];
55
- if (!types.some((t) => matchesType(value, t))) {
56
- errors.push(`${pointer || '/'}: expected ${types.join(' | ')}, got ${typeOf(value)}`);
57
- return; // further checks assume the type held
58
- }
59
- }
60
-
61
- if (Object.prototype.hasOwnProperty.call(schema, 'const') && JSON.stringify(value) !== JSON.stringify(schema.const)) {
62
- errors.push(`${pointer || '/'}: must equal ${JSON.stringify(schema.const)}`);
63
- }
64
-
65
- if (schema.enum && !schema.enum.some((option) => JSON.stringify(option) === JSON.stringify(value))) {
66
- errors.push(`${pointer || '/'}: ${JSON.stringify(value)} is not one of ${JSON.stringify(schema.enum)}`);
67
- }
68
-
69
- if (typeof value === 'string') {
70
- if (schema.pattern && !new RegExp(schema.pattern).test(value)) {
71
- errors.push(`${pointer || '/'}: "${value}" does not match /${schema.pattern}/`);
72
- }
73
- if (schema.format === 'date-time' && !DATE_TIME.test(value)) {
74
- errors.push(`${pointer || '/'}: "${value}" is not a valid date-time`);
75
- }
76
- if (schema.minLength != null && value.length < schema.minLength) {
77
- errors.push(`${pointer || '/'}: shorter than minLength ${schema.minLength}`);
78
- }
79
- if (schema.maxLength != null && value.length > schema.maxLength) {
80
- errors.push(`${pointer || '/'}: longer than maxLength ${schema.maxLength}`);
81
- }
82
- }
83
-
84
- if (typeof value === 'number') {
85
- if (schema.minimum != null && value < schema.minimum) {
86
- errors.push(`${pointer || '/'}: ${value} is below minimum ${schema.minimum}`);
87
- }
88
- if (schema.maximum != null && value > schema.maximum) {
89
- errors.push(`${pointer || '/'}: ${value} is above maximum ${schema.maximum}`);
90
- }
91
- }
92
-
93
- if (Array.isArray(value)) {
94
- if (schema.minItems != null && value.length < schema.minItems) {
95
- errors.push(`${pointer || '/'}: has ${value.length} items, needs at least ${schema.minItems}`);
96
- }
97
- if (schema.maxItems != null && value.length > schema.maxItems) {
98
- errors.push(`${pointer || '/'}: has ${value.length} items, allows at most ${schema.maxItems}`);
99
- }
100
- if (schema.items) {
101
- value.forEach((item, index) => validateNode(item, schema.items, `${pointer}/${index}`, errors));
102
- }
103
- }
104
-
105
- if (matchesType(value, 'object') && (schema.properties || schema.required || schema.additionalProperties === false)) {
106
- const properties = schema.properties ?? {};
107
- for (const name of schema.required ?? []) {
108
- if (!Object.prototype.hasOwnProperty.call(value, name)) {
109
- errors.push(`${pointer || '/'}: missing required property "${name}"`);
110
- }
111
- }
112
- for (const [name, child] of Object.entries(value)) {
113
- if (properties[name]) {
114
- validateNode(child, properties[name], `${pointer}/${name}`, errors);
115
- } else if (schema.additionalProperties === false) {
116
- errors.push(`${pointer || '/'}: unexpected property "${name}"`);
117
- }
118
- }
119
- }
120
-
121
- // Applicators last: the invariant layer. `additionalProperties` deliberately
122
- // does not see properties introduced by these subschemas (JSON Schema
123
- // 2020-12 rule, matching the Python twin).
124
- for (const sub of schema.allOf ?? []) {
125
- validateNode(value, sub, pointer, errors);
126
- }
127
-
128
- if (Object.prototype.hasOwnProperty.call(schema, 'if')) {
129
- const branch = satisfies(value, schema.if) ? schema.then : schema.else;
130
- if (branch != null) validateNode(value, branch, pointer, errors);
131
- }
132
- }
133
-
134
- /** Validate `value` against `schema`; returns an array of error strings (empty = valid). */
135
- export function validate(value, schema) {
136
- const errors = [];
137
- validateNode(value, schema, '', errors);
138
- return errors;
139
- }
140
-
141
- /** The supported keyword subset, exported so tooling can assert parity. */
142
- export const SUPPORTED_KEYWORDS = Object.freeze([...SUPPORTED].sort());