fix(qa,qa-only): define evidence layout edge cases; drop unavailable recording

Remove $B record / repro.webm advertising (browse has no recorder yet). Document
flat-default vs --evidence-per-finding nested layout, deterministic collision
handling (no silent overwrite; sequential NNN and -2/-3 suffixes), and
missing/unreadable asset rules (report and continue; never invent files). Add
focused contract tests for both /qa and /qa-only.
This commit is contained in:
Tim White 2026-07-20 14:33:28 -07:00
parent 07ca46b9e0
commit b70483495b
7 changed files with 243 additions and 31 deletions

View File

@ -1054,9 +1054,9 @@ $B snapshot -i -a -o "$REPORT_DIR/screenshots/issue-002.png"
#### Evidence layout: flat (default) vs per-finding
By default, evidence files share one `screenshots/` directory and the report references them by filename (`issue-001-step-1.png`, etc). This stays compact and works well for 1-5 findings.
**Default is flat.** Evidence files share one `screenshots/` directory and the report references them by filename (`issue-001-step-1.png`, etc). Flat is used unless the user explicitly opts in. This stays compact and works well for 1-5 findings.
When the run is invoked with **`--evidence-per-finding`**, switch to one folder per finding:
**Opt-in nested layout.** When the run is invoked with **`--evidence-per-finding`** (or natural-language variants: `evidence per finding`, `one folder per bug`), switch to one folder per finding:
```
.gstack/qa-reports/
@ -1065,10 +1065,9 @@ When the run is invoked with **`--evidence-per-finding`**, switch to one folder
├── findings/
│ ├── 001-critical-checkout-500-on-submit/
│ │ ├── finding.md # severity + repro + env + expected/actual
│ │ ├── step-1.png # before action
│ │ ├── step-2.png # after action
│ │ ├── result.png # final state
│ │ └── repro.webm # OPTIONAL — present iff `$B record` was active
│ │ ├── step-1.png # before action (when capture succeeded)
│ │ ├── step-2.png # after action (when capture succeeded)
│ │ └── result.png # final state (when capture succeeded)
│ ├── 002-high-search-no-results/
│ │ └── ...
│ └── 003-low-cosmetic-spacing/
@ -1076,6 +1075,36 @@ When the run is invoked with **`--evidence-per-finding`**, switch to one folder
└── baseline.json # unchanged
```
**Directory / file creation and naming:**
1. **Flat (default):**
- Ensure `.gstack/qa-reports/screenshots/` exists (`mkdir -p`).
- Report file: `qa-report-{domain}-{YYYY-MM-DD}.md` under the output dir.
- Evidence files: `issue-{NNN}-step-{K}.png`, `issue-{NNN}-result.png` (and for /qa fix loop: `issue-{NNN}-before.png` / `issue-{NNN}-after.png`).
- `{NNN}` is a sequential zero-padded index starting at `001`. Assign the next free index by scanning existing `issue-*.png` / report issue IDs and taking max + 1.
2. **Per-finding (opt-in):**
- Create a per-run report directory: `qa-report-{domain}-{YYYY-MM-DD}/` with `findings/` inside (`mkdir -p`).
- Top-level report is `REPORT.md` inside that directory (not a sibling `.md` file).
- Each finding folder: `{NNN}-{severity}-{kebab-slug}/` where `{NNN}` is sequential (`001`, `002`, …), severity is lowercase (`critical`/`high`/`medium`/`low`/`cosmetic`), and slug is a short kebab-case title stem (alphanumeric + hyphens only, max ~40 chars).
- Inside each finding folder write `finding.md` first, then only the screenshot files that capture actually produced (`step-1.png`, `step-2.png`, `result.png`; /qa fix loop may add `before.png` / `after.png`).
- Create a finding folder only when that finding is documented — do not pre-create empty finding dirs.
**Collisions (must not silently overwrite evidence):**
- Never overwrite an existing evidence file or finding directory. If a target path already exists, pick a deterministic free name:
- **Sequential IDs first:** prefer the next free `{NNN}` (flat filenames and nested folder prefixes). Scan existing siblings, take max + 1.
- **Same-day report path collision:** if `qa-report-{domain}-{YYYY-MM-DD}.md` (flat) or `qa-report-{domain}-{YYYY-MM-DD}/` (nested) already exists from an earlier run, append `-2`, then `-3`, etc. (`qa-report-{domain}-{YYYY-MM-DD}-2.md` / `…-2/`) until the path is free. Do not clobber the prior run.
- **Nested folder slug collision under the same NNN:** if `findings/{NNN}-{severity}-{slug}/` exists, append `-2`, `-3`, … to the folder name before writing.
- **Flat filename collision:** if `issue-{NNN}-….png` exists for the chosen NNN, bump NNN (auto-increment) rather than overwriting.
- This matches the existing regression-test collision rule: check existing names, take max number + 1 / next free suffix — never silent overwrite.
**Missing or unreadable evidence assets:**
- Capture failures are normal (timeout, navigation error, permission). **Do not invent placeholder image files** and do not write empty/dummy PNGs.
- In `finding.md` / the report **Evidence** section, list only files that exist on disk after a successful write. For a failed capture, note it inline, e.g. `_(capture failed: {short reason})_`, and continue the run.
- If a referenced path is unreadable at report time, treat it as missing: omit the file link, note `_(unreadable: {path})_`, continue. Never block the rest of documentation on one bad asset.
- Top-level `REPORT.md` / flat report screenshot counts must reflect files that actually exist.
**finding.md** (per-finding) MUST have this shape:
```markdown
@ -1107,18 +1136,18 @@ When the run is invoked with **`--evidence-per-finding`**, switch to one folder
- `step-1.png` — before the action
- `step-2.png` — after the action
- `result.png` — final state
- `repro.webm` — full interactive repro (if recorded)
```
(Only list evidence files that exist. If a step capture failed, replace that bullet with `_(capture failed: {reason})_`.)
**When per-finding is the right call:**
- Run produces ≥5 findings (the flat layout gets noisy past that).
- Any finding is critical or high severity — those tickets travel further, need self-contained evidence.
- An interactive bug needs video evidence — `record start` then `record stop` (Playwright `recordVideo`) writes a `.webm`; move it into the corresponding finding folder during Phase 6.
- Findings will be handed off as Linear/Jira tickets — each folder zips into a self-contained attachment.
**When per-finding is overkill:** quick smoke runs, 1-2 findings, regression-mode reruns where the baseline is the canonical artifact. Stick with the flat layout.
Top-level `REPORT.md` content is identical between the two layouts; only the on-disk filing differs.
Top-level `REPORT.md` content is identical between the two layouts; only the on-disk filing differs. Screenshots remain the evidence format — video recording is not part of this layout.
### Phase 6: Wrap Up
@ -1259,7 +1288,7 @@ Write to `~/.gstack/projects/{slug}/{user}-{branch}-test-outcome-{datetime}.md`
Report filenames use the domain and date: `qa-report-myapp-com-2026-03-12.md`
**With `--evidence-per-finding`:** one folder per finding under a per-run report dir. Each folder is self-contained — `finding.md` plus step screenshots and (if `$B record` was active) a `repro.webm`. See "Evidence layout: flat vs per-finding" in the Document phase above for the full structure and `finding.md` template.
**With `--evidence-per-finding`:** one folder per finding under a per-run report dir. Each folder is self-contained — `finding.md` plus step screenshots when those captures succeeded. Default remains flat (`screenshots/` + single report `.md`). Nested mode must not silently overwrite prior evidence; see "Evidence layout: flat vs per-finding" in the Document phase above for naming, collision, and missing-asset rules.
---

View File

@ -107,7 +107,7 @@ Write to `~/.gstack/projects/{slug}/{user}-{branch}-test-outcome-{datetime}.md`
Report filenames use the domain and date: `qa-report-myapp-com-2026-03-12.md`
**With `--evidence-per-finding`:** one folder per finding under a per-run report dir. Each folder is self-contained — `finding.md` plus step screenshots and (if `$B record` was active) a `repro.webm`. See "Evidence layout: flat vs per-finding" in the Document phase above for the full structure and `finding.md` template.
**With `--evidence-per-finding`:** one folder per finding under a per-run report dir. Each folder is self-contained — `finding.md` plus step screenshots when those captures succeeded. Default remains flat (`screenshots/` + single report `.md`). Nested mode must not silently overwrite prior evidence; see "Evidence layout: flat vs per-finding" in the Document phase above for naming, collision, and missing-asset rules.
---

View File

@ -1288,9 +1288,9 @@ $B snapshot -i -a -o "$REPORT_DIR/screenshots/issue-002.png"
#### Evidence layout: flat (default) vs per-finding
By default, evidence files share one `screenshots/` directory and the report references them by filename (`issue-001-step-1.png`, etc). This stays compact and works well for 1-5 findings.
**Default is flat.** Evidence files share one `screenshots/` directory and the report references them by filename (`issue-001-step-1.png`, etc). Flat is used unless the user explicitly opts in. This stays compact and works well for 1-5 findings.
When the run is invoked with **`--evidence-per-finding`**, switch to one folder per finding:
**Opt-in nested layout.** When the run is invoked with **`--evidence-per-finding`** (or natural-language variants: `evidence per finding`, `one folder per bug`), switch to one folder per finding:
```
.gstack/qa-reports/
@ -1299,10 +1299,9 @@ When the run is invoked with **`--evidence-per-finding`**, switch to one folder
├── findings/
│ ├── 001-critical-checkout-500-on-submit/
│ │ ├── finding.md # severity + repro + env + expected/actual
│ │ ├── step-1.png # before action
│ │ ├── step-2.png # after action
│ │ ├── result.png # final state
│ │ └── repro.webm # OPTIONAL — present iff `$B record` was active
│ │ ├── step-1.png # before action (when capture succeeded)
│ │ ├── step-2.png # after action (when capture succeeded)
│ │ └── result.png # final state (when capture succeeded)
│ ├── 002-high-search-no-results/
│ │ └── ...
│ └── 003-low-cosmetic-spacing/
@ -1310,6 +1309,36 @@ When the run is invoked with **`--evidence-per-finding`**, switch to one folder
└── baseline.json # unchanged
```
**Directory / file creation and naming:**
1. **Flat (default):**
- Ensure `.gstack/qa-reports/screenshots/` exists (`mkdir -p`).
- Report file: `qa-report-{domain}-{YYYY-MM-DD}.md` under the output dir.
- Evidence files: `issue-{NNN}-step-{K}.png`, `issue-{NNN}-result.png` (and for /qa fix loop: `issue-{NNN}-before.png` / `issue-{NNN}-after.png`).
- `{NNN}` is a sequential zero-padded index starting at `001`. Assign the next free index by scanning existing `issue-*.png` / report issue IDs and taking max + 1.
2. **Per-finding (opt-in):**
- Create a per-run report directory: `qa-report-{domain}-{YYYY-MM-DD}/` with `findings/` inside (`mkdir -p`).
- Top-level report is `REPORT.md` inside that directory (not a sibling `.md` file).
- Each finding folder: `{NNN}-{severity}-{kebab-slug}/` where `{NNN}` is sequential (`001`, `002`, …), severity is lowercase (`critical`/`high`/`medium`/`low`/`cosmetic`), and slug is a short kebab-case title stem (alphanumeric + hyphens only, max ~40 chars).
- Inside each finding folder write `finding.md` first, then only the screenshot files that capture actually produced (`step-1.png`, `step-2.png`, `result.png`; /qa fix loop may add `before.png` / `after.png`).
- Create a finding folder only when that finding is documented — do not pre-create empty finding dirs.
**Collisions (must not silently overwrite evidence):**
- Never overwrite an existing evidence file or finding directory. If a target path already exists, pick a deterministic free name:
- **Sequential IDs first:** prefer the next free `{NNN}` (flat filenames and nested folder prefixes). Scan existing siblings, take max + 1.
- **Same-day report path collision:** if `qa-report-{domain}-{YYYY-MM-DD}.md` (flat) or `qa-report-{domain}-{YYYY-MM-DD}/` (nested) already exists from an earlier run, append `-2`, then `-3`, etc. (`qa-report-{domain}-{YYYY-MM-DD}-2.md` / `…-2/`) until the path is free. Do not clobber the prior run.
- **Nested folder slug collision under the same NNN:** if `findings/{NNN}-{severity}-{slug}/` exists, append `-2`, `-3`, … to the folder name before writing.
- **Flat filename collision:** if `issue-{NNN}-….png` exists for the chosen NNN, bump NNN (auto-increment) rather than overwriting.
- This matches the existing regression-test collision rule: check existing names, take max number + 1 / next free suffix — never silent overwrite.
**Missing or unreadable evidence assets:**
- Capture failures are normal (timeout, navigation error, permission). **Do not invent placeholder image files** and do not write empty/dummy PNGs.
- In `finding.md` / the report **Evidence** section, list only files that exist on disk after a successful write. For a failed capture, note it inline, e.g. `_(capture failed: {short reason})_`, and continue the run.
- If a referenced path is unreadable at report time, treat it as missing: omit the file link, note `_(unreadable: {path})_`, continue. Never block the rest of documentation on one bad asset.
- Top-level `REPORT.md` / flat report screenshot counts must reflect files that actually exist.
**finding.md** (per-finding) MUST have this shape:
```markdown
@ -1341,18 +1370,18 @@ When the run is invoked with **`--evidence-per-finding`**, switch to one folder
- `step-1.png` — before the action
- `step-2.png` — after the action
- `result.png` — final state
- `repro.webm` — full interactive repro (if recorded)
```
(Only list evidence files that exist. If a step capture failed, replace that bullet with `_(capture failed: {reason})_`.)
**When per-finding is the right call:**
- Run produces ≥5 findings (the flat layout gets noisy past that).
- Any finding is critical or high severity — those tickets travel further, need self-contained evidence.
- An interactive bug needs video evidence — `record start` then `record stop` (Playwright `recordVideo`) writes a `.webm`; move it into the corresponding finding folder during Phase 6.
- Findings will be handed off as Linear/Jira tickets — each folder zips into a self-contained attachment.
**When per-finding is overkill:** quick smoke runs, 1-2 findings, regression-mode reruns where the baseline is the canonical artifact. Stick with the flat layout.
Top-level `REPORT.md` content is identical between the two layouts; only the on-disk filing differs.
Top-level `REPORT.md` content is identical between the two layouts; only the on-disk filing differs. Screenshots remain the evidence format — video recording is not part of this layout.
### Phase 6: Wrap Up
@ -1485,7 +1514,7 @@ Record baseline health score at end of Phase 6.
Report filenames use the domain and date: `qa-report-myapp-com-2026-03-12.md`
**With `--evidence-per-finding`:** one folder per finding under a per-run report dir. Each folder is self-contained — `finding.md` plus step screenshots, fix before/after, and (if `$B record` was active) a `repro.webm`. See "Evidence layout: flat vs per-finding" in the Document phase below for the full structure and `finding.md` template.
**With `--evidence-per-finding`:** one folder per finding under a per-run report dir. Each folder is self-contained — `finding.md` plus step screenshots and fix before/after when those captures succeeded. Default remains flat (`screenshots/` + single report `.md`). Nested mode must not silently overwrite prior evidence; see "Evidence layout: flat vs per-finding" in the Document phase (Phases 1-6) for naming, collision, and missing-asset rules.
---

View File

@ -145,7 +145,7 @@ Record baseline health score at end of Phase 6.
Report filenames use the domain and date: `qa-report-myapp-com-2026-03-12.md`
**With `--evidence-per-finding`:** one folder per finding under a per-run report dir. Each folder is self-contained — `finding.md` plus step screenshots, fix before/after, and (if `$B record` was active) a `repro.webm`. See "Evidence layout: flat vs per-finding" in the Document phase below for the full structure and `finding.md` template.
**With `--evidence-per-finding`:** one folder per finding under a per-run report dir. Each folder is self-contained — `finding.md` plus step screenshots and fix before/after when those captures succeeded. Default remains flat (`screenshots/` + single report `.md`). Nested mode must not silently overwrite prior evidence; see "Evidence layout: flat vs per-finding" in the Document phase (Phases 1-6) for naming, collision, and missing-asset rules.
---

View File

@ -259,9 +259,9 @@ $B snapshot -i -a -o "$REPORT_DIR/screenshots/issue-002.png"
#### Evidence layout: flat (default) vs per-finding
By default, evidence files share one \`screenshots/\` directory and the report references them by filename (\`issue-001-step-1.png\`, etc). This stays compact and works well for 1-5 findings.
**Default is flat.** Evidence files share one \`screenshots/\` directory and the report references them by filename (\`issue-001-step-1.png\`, etc). Flat is used unless the user explicitly opts in. This stays compact and works well for 1-5 findings.
When the run is invoked with **\`--evidence-per-finding\`**, switch to one folder per finding:
**Opt-in nested layout.** When the run is invoked with **\`--evidence-per-finding\`** (or natural-language variants: \`evidence per finding\`, \`one folder per bug\`), switch to one folder per finding:
\`\`\`
.gstack/qa-reports/
@ -270,10 +270,9 @@ When the run is invoked with **\`--evidence-per-finding\`**, switch to one folde
findings/
001-critical-checkout-500-on-submit/
finding.md # severity + repro + env + expected/actual
step-1.png # before action
step-2.png # after action
result.png # final state
repro.webm # OPTIONAL present iff \`$B record\` was active
step-1.png # before action (when capture succeeded)
step-2.png # after action (when capture succeeded)
result.png # final state (when capture succeeded)
002-high-search-no-results/
...
003-low-cosmetic-spacing/
@ -281,6 +280,36 @@ When the run is invoked with **\`--evidence-per-finding\`**, switch to one folde
baseline.json # unchanged
\`\`\`
**Directory / file creation and naming:**
1. **Flat (default):**
- Ensure \`.gstack/qa-reports/screenshots/\` exists (\`mkdir -p\`).
- Report file: \`qa-report-{domain}-{YYYY-MM-DD}.md\` under the output dir.
- Evidence files: \`issue-{NNN}-step-{K}.png\`, \`issue-{NNN}-result.png\` (and for /qa fix loop: \`issue-{NNN}-before.png\` / \`issue-{NNN}-after.png\`).
- \`{NNN}\` is a sequential zero-padded index starting at \`001\`. Assign the next free index by scanning existing \`issue-*.png\` / report issue IDs and taking max + 1.
2. **Per-finding (opt-in):**
- Create a per-run report directory: \`qa-report-{domain}-{YYYY-MM-DD}/\` with \`findings/\` inside (\`mkdir -p\`).
- Top-level report is \`REPORT.md\` inside that directory (not a sibling \`.md\` file).
- Each finding folder: \`{NNN}-{severity}-{kebab-slug}/\` where \`{NNN}\` is sequential (\`001\`, \`002\`, …), severity is lowercase (\`critical\`/\`high\`/\`medium\`/\`low\`/\`cosmetic\`), and slug is a short kebab-case title stem (alphanumeric + hyphens only, max ~40 chars).
- Inside each finding folder write \`finding.md\` first, then only the screenshot files that capture actually produced (\`step-1.png\`, \`step-2.png\`, \`result.png\`; /qa fix loop may add \`before.png\` / \`after.png\`).
- Create a finding folder only when that finding is documented do not pre-create empty finding dirs.
**Collisions (must not silently overwrite evidence):**
- Never overwrite an existing evidence file or finding directory. If a target path already exists, pick a deterministic free name:
- **Sequential IDs first:** prefer the next free \`{NNN}\` (flat filenames and nested folder prefixes). Scan existing siblings, take max + 1.
- **Same-day report path collision:** if \`qa-report-{domain}-{YYYY-MM-DD}.md\` (flat) or \`qa-report-{domain}-{YYYY-MM-DD}/\` (nested) already exists from an earlier run, append \`-2\`, then \`-3\`, etc. (\`qa-report-{domain}-{YYYY-MM-DD}-2.md\` / \`…-2/\`) until the path is free. Do not clobber the prior run.
- **Nested folder slug collision under the same NNN:** if \`findings/{NNN}-{severity}-{slug}/\` exists, append \`-2\`, \`-3\`, … to the folder name before writing.
- **Flat filename collision:** if \`issue-{NNN}-….png\` exists for the chosen NNN, bump NNN (auto-increment) rather than overwriting.
- This matches the existing regression-test collision rule: check existing names, take max number + 1 / next free suffix never silent overwrite.
**Missing or unreadable evidence assets:**
- Capture failures are normal (timeout, navigation error, permission). **Do not invent placeholder image files** and do not write empty/dummy PNGs.
- In \`finding.md\` / the report **Evidence** section, list only files that exist on disk after a successful write. For a failed capture, note it inline, e.g. \`_(capture failed: {short reason})_\`, and continue the run.
- If a referenced path is unreadable at report time, treat it as missing: omit the file link, note \`_(unreadable: {path})_\`, continue. Never block the rest of documentation on one bad asset.
- Top-level \`REPORT.md\` / flat report screenshot counts must reflect files that actually exist.
**finding.md** (per-finding) MUST have this shape:
\`\`\`markdown
@ -312,18 +341,18 @@ When the run is invoked with **\`--evidence-per-finding\`**, switch to one folde
- \`step-1.png\` — before the action
- \`step-2.png\` — after the action
- \`result.png\` — final state
- \`repro.webm\` — full interactive repro (if recorded)
\`\`\`
(Only list evidence files that exist. If a step capture failed, replace that bullet with \`_(capture failed: {reason})_\`.)
**When per-finding is the right call:**
- Run produces 5 findings (the flat layout gets noisy past that).
- Any finding is critical or high severity those tickets travel further, need self-contained evidence.
- An interactive bug needs video evidence \`record start\` then \`record stop\` (Playwright \`recordVideo\`) writes a \`.webm\`; move it into the corresponding finding folder during Phase 6.
- Findings will be handed off as Linear/Jira tickets each folder zips into a self-contained attachment.
**When per-finding is overkill:** quick smoke runs, 1-2 findings, regression-mode reruns where the baseline is the canonical artifact. Stick with the flat layout.
Top-level \`REPORT.md\` content is identical between the two layouts; only the on-disk filing differs.
Top-level \`REPORT.md\` content is identical between the two layouts; only the on-disk filing differs. Screenshots remain the evidence format — video recording is not part of this layout.
### Phase 6: Wrap Up

View File

@ -476,6 +476,101 @@ describe('gen-skill-docs', () => {
});
});
/**
* Evidence layout contract for /qa and /qa-only.
*
* Shared methodology (via {{QA_METHODOLOGY}}) plus each leaf's Setup row and
* Output Structure pointer must stay aligned: flat default, opt-in nested
* layout, no unavailable recording claims, explicit collision and missing-asset
* rules.
*/
describe('QA evidence layout contract (qa + qa-only)', () => {
const qaContent = () => fs.readFileSync(path.join(ROOT, 'qa', 'SKILL.md'), 'utf-8');
const qaOnlyContent = () => fs.readFileSync(path.join(ROOT, 'qa-only', 'SKILL.md'), 'utf-8');
const both = () => [
{ name: 'qa', content: qaContent() },
{ name: 'qa-only', content: qaOnlyContent() },
];
test('both modes document flat default and --evidence-per-finding opt-in', () => {
for (const { name, content } of both()) {
expect(content, name).toContain('--evidence-per-finding');
expect(content, name).toMatch(/Evidence layout.*flat \(default\)/s);
expect(content, name).toContain('Evidence layout: flat (default) vs per-finding');
expect(content, name).toContain('findings/');
expect(content, name).toContain('finding.md');
// Flat remains the default on-disk shape
expect(content, name).toContain('screenshots/');
expect(content, name).toMatch(/Default \(flat layout\)/);
}
});
test('both modes define finding folder naming and nested tree shape', () => {
for (const { name, content } of both()) {
// Canonical nested paths from the shared methodology section
expect(content, name).toContain('001-critical-checkout-500-on-submit/');
expect(content, name).toContain('step-1.png');
expect(content, name).toContain('result.png');
expect(content, name).toContain('REPORT.md');
// finding.md shape fields
expect(content, name).toContain('**Severity:**');
expect(content, name).toContain('## Repro steps');
expect(content, name).toContain('## Evidence');
}
});
test('both modes ban unavailable video recording claims', () => {
for (const { name, content } of both()) {
// $B record / recordVideo / repro.webm are not implemented in browse —
// skills must not advertise them (reviewer feedback on PR #1484).
expect(content, name).not.toMatch(/\$B record\b/);
expect(content, name).not.toMatch(/\brepro\.webm\b/);
expect(content, name).not.toMatch(/\brecordVideo\b/);
expect(content, name).not.toMatch(/`record start`/);
expect(content, name).not.toMatch(/`record stop`/);
}
});
test('both modes specify collision handling (no silent overwrite)', () => {
for (const { name, content } of both()) {
expect(content, name).toMatch(/collision|must not silently overwrite|never overwrite/i);
// Deterministic conflict resolution: sequential NNN and/or -2, -3 suffix
expect(content, name).toMatch(/-2|-3|auto-increment|sequential/i);
}
});
test('both modes specify missing/unreadable evidence asset behavior', () => {
for (const { name, content } of both()) {
expect(content, name).toMatch(/missing|unreadable|capture failed/i);
// Do not invent placeholder files; report and continue
expect(content, name).toMatch(/do not invent|must not invent|never invent|omit/i);
}
});
test('leaf templates carry Setup row and Output Structure pointer without recording', () => {
const qaTmpl = fs.readFileSync(path.join(ROOT, 'qa', 'SKILL.md.tmpl'), 'utf-8');
const qaOnlyTmpl = fs.readFileSync(path.join(ROOT, 'qa-only', 'SKILL.md.tmpl'), 'utf-8');
for (const { name, tmpl } of [
{ name: 'qa', tmpl: qaTmpl },
{ name: 'qa-only', tmpl: qaOnlyTmpl },
]) {
expect(tmpl, name).toContain('| Evidence layout | flat (default) |');
expect(tmpl, name).toContain('`--evidence-per-finding`');
expect(tmpl, name).toMatch(/With `--evidence-per-finding`:/);
expect(tmpl, name).not.toMatch(/\$B record\b/);
expect(tmpl, name).not.toMatch(/\brepro\.webm\b/);
}
});
test('/qa nested pointer keeps fix before/after; /qa-only does not claim fix assets', () => {
const qaTmpl = fs.readFileSync(path.join(ROOT, 'qa', 'SKILL.md.tmpl'), 'utf-8');
const qaOnlyTmpl = fs.readFileSync(path.join(ROOT, 'qa-only', 'SKILL.md.tmpl'), 'utf-8');
expect(qaTmpl).toMatch(/fix before\/after|before\/after/);
// qa-only is report-only — no fix-loop evidence language required
expect(qaOnlyTmpl).toContain('finding.md');
});
});
describe('BASE_BRANCH_DETECT resolver', () => {
// Find a generated SKILL.md that uses the placeholder (ship is guaranteed to)
const shipContent = fs.readFileSync(path.join(ROOT, 'ship', 'SKILL.md'), 'utf-8');

View File

@ -421,6 +421,36 @@ describe('QA skill structure validation', () => {
expect(qaContent).toContain('screenshots/');
expect(qaContent).toContain('.gstack/qa-reports/');
});
test('evidence layout: flat default, opt-in nested, no recording claims', () => {
expect(qaContent).toContain('--evidence-per-finding');
expect(qaContent).toContain('Evidence layout: flat (default) vs per-finding');
expect(qaContent).toContain('findings/');
expect(qaContent).toContain('finding.md');
// Unavailable browse capability — must not advertise
expect(qaContent).not.toMatch(/\brepro\.webm\b/);
expect(qaContent).not.toMatch(/\$B record\b/);
expect(qaContent).not.toMatch(/\brecordVideo\b/);
// Edge cases required by the layout contract
expect(qaContent).toMatch(/must not silently overwrite|never overwrite/i);
expect(qaContent).toMatch(/missing|unreadable|capture failed/i);
});
});
describe('QA-only evidence layout contract', () => {
const qaOnlyContent = fs.readFileSync(path.join(ROOT, 'qa-only', 'SKILL.md'), 'utf-8');
test('mirrors nested evidence contract without recording claims', () => {
expect(qaOnlyContent).toContain('--evidence-per-finding');
expect(qaOnlyContent).toContain('Evidence layout: flat (default) vs per-finding');
expect(qaOnlyContent).toContain('findings/');
expect(qaOnlyContent).toContain('finding.md');
expect(qaOnlyContent).not.toMatch(/\brepro\.webm\b/);
expect(qaOnlyContent).not.toMatch(/\$B record\b/);
expect(qaOnlyContent).not.toMatch(/\brecordVideo\b/);
expect(qaOnlyContent).toMatch(/must not silently overwrite|never overwrite/i);
expect(qaOnlyContent).toMatch(/missing|unreadable|capture failed/i);
});
});
// --- Part 7: Greptile history format consistency (A3) ---