Skip to content

Commit f49ad8c

Browse files
authored
Merge pull request #290 from Programcode/t3/agentic-bugbash-1101
Triage the Nov 1 bug bash: name sides on Current Debate, reject blank input, stop false-red explore jobs
2 parents a6aea25 + 66cc6f0 commit f49ad8c

11 files changed

Lines changed: 56 additions & 17 deletions

File tree

‎.claude/skills/verify-debatelab/features/current-debate.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ Preconditions:
3030
- **Scope.** Add a source on `/app/library` with a unique marker (see [library-sources](./library-sources.md)); the test id `library-debate` names the motion. Run Brainstorm or Generate: the motion field already holds the debate's motion and no dialog opens. End state: the newest `providerCalls()` entry for the feature has this debate's marker in `system` and not another debate's. `sources.debate_id`, `cases.debate_id`, and `brainstorms.debate_id` point at the debate they were made in.
3131
- **Offer.** With no debate, fill a tool's motion and submit. The dialog `Start a debate with this motion?` contains the motion. Click its `Start debate`. End state: the result shows, the strip reads `Working in: <motion>`, and `debates` holds exactly that motion.
3232
- **Use once.** With a debate, submit a different motion. The dialog `This is a different motion` opens. `chooseForMotion(page, "once")`. End state: the provider call's `system` has no debate marker, and the header still names the old debate.
33-
- **Dashboard.** `/app/debate`: `current-debate-motion` has the motion, `debate-side-pro` reads `In favor`, `debate-side-con` reads `Against`, and `debate-sources` and `debate-recent` list only this debate's work.
33+
- **Dashboard.** `/app/debate`: `current-debate-motion` has the motion, `debate-side-pro` reads `In favor`, `debate-side-con` reads `Against`, and `debate-sources` and `debate-recent` list only this debate's work. Each row reads `<kind> · <side> · <time>` (a side column leaves out its own side), and its Load button's name ends with the title, kind and side.
3434
- **Switch.** Click `debateControl(page)`, then the dialog's `Switch debate`, then `Switch to <motion>`. End state: the Library shows only that debate's sources. In History, the `Brainstorms` and `Cases` tabs show only its results.
3535
- **Tour.** Seed `debate-trainer-onboarding` with `hasCompletedOnboarding: false` once per session (see the recipe), sign in, and click the tooltip's `Start debate`. Fill `Motion`, click `Start debate`, click `[data-test-id="button-primary"]` twice, then `Get started`. End state: one `debates` row is current, and Generate opens prefilled with no dialog.
3636

‎.claude/skills/verify-debatelab/recipes/current-debate.spec.ts‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,8 @@ test("debate-start, debate-scope, debate-once, debate-switch: two debates keep t
118118
await expect(page.getByTestId("debate-sources")).toContainText("Zoo study");
119119
await expect(page.getByTestId("debate-sources")).not.toContainText("Homework study");
120120
await expect(page.getByTestId("debate-recent")).toContainText(ZOOS);
121+
// Every row is titled with the motion, so the side tells a Pro case from a Con one.
122+
await expect(page.getByTestId("debate-recent")).toContainText("Case · In favor");
121123
await evidence.shot("05-zoo-dashboard");
122124

123125
// Switching back switches the Library and History with it.

‎.github/workflows/agentic-e2e.yml‎

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -152,9 +152,9 @@ jobs:
152152
needs: plan-explore
153153
runs-on: ubuntu-latest
154154
timeout-minutes: 45
155-
# Findings are leads to reproduce, not verdicts: a red exploration does not
156-
# fail the workflow. docs/testing.md has the bug-bash procedure.
157-
continue-on-error: true
155+
# Findings are leads to reproduce, not verdicts, so they pass with a warning;
156+
# a broken run (crash, missing key, stack down) fails the job.
157+
# docs/testing.md has the bug-bash procedure.
158158
strategy:
159159
fail-fast: false
160160
matrix:
@@ -198,10 +198,21 @@ jobs:
198198
GOAL: ${{ matrix.leg.goal }}
199199
PERSONA: ${{ matrix.leg.persona }}
200200
TARGET: ${{ matrix.leg.target }}
201-
run: >-
202-
.claude/skills/verify-debatelab/bin/verify.sh e2e explore "$GOAL"
203-
--session student --agent "$PERSONA" --target "$TARGET"
204-
--trace=on --video=on --reporter list,markdown
201+
# e2e explore exits 1 when it reports an issue, and also when no step ran.
202+
# Only the first is the bug bash's output; every other non-zero exit is
203+
# the harness or the stack failing.
204+
run: |
205+
status=0
206+
.claude/skills/verify-debatelab/bin/verify.sh e2e explore "$GOAL" \
207+
--session student --agent "$PERSONA" --target "$TARGET" \
208+
--trace=on --video=on --reporter list,markdown || status=$?
209+
report=$(ls -d .verify/artifacts/*-e2e-explore-*/report.json 2>/dev/null | tail -n 1)
210+
issues=$(jq '[.run.explore.findings[]? | select(.kind == "issue")] | length' "$report" 2>/dev/null || echo 0)
211+
if [ "$status" -eq 1 ] && [ "${issues:-0}" -gt 0 ]; then
212+
echo "::warning title=Exploration findings::$PERSONA on $TARGET reported $issues issues to reproduce; see the job summary."
213+
exit 0
214+
fi
215+
exit "$status"
205216
206217
- name: Publish the report
207218
if: always()

‎docs/testing.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -269,7 +269,7 @@ To write a test, import `test`, `expect`, and the fixtures from [`agentic/suppor
269269

270270
A bug bash runs several explorations in parallel, one charter per area and persona, then proves each finding with a repro test. Ask an agent to "bug bash <area>"; the `e2e` skill's `bug-bash` topic has the procedure. A finding counts only when its repro test fails for the reported reason. Tag repro tests `{ tags: ["repro"] }` so the scheduled suite skips them until the fix lands, then remove the tag so they become regression tests. The client runs on a Vite dev server, so a route that is still compiling can look like a dead link to an explorer. Reproduce before you believe it.
271271

272-
[`.github/workflows/agentic-e2e.yml`](../.github/workflows/agentic-e2e.yml) runs the suite on Wednesdays at 03:47 UTC and on demand (`gh workflow run agentic-e2e.yml --ref <branch>`), never on pull requests, in four jobs: the page sweep and everything else, on each viewport. On the 1st of each month at 05:13 UTC it runs a bug bash instead: one exploration per persona, each with its own charter. On demand with a goal, it runs one exploration with the chosen persona and viewport. It uses the `LIVE_OPENROUTER_API_KEY` secret (the `E2E_AGENT_MODEL` and `E2E_JUDGE_MODEL` repository variables override the models), fails when the key is missing, and writes the report to the job summary. Explorations never fail the workflow: their findings are leads to reproduce. The suite is not a required check: model judgments can flake, and a red weekly run is a finding to triage.
272+
[`.github/workflows/agentic-e2e.yml`](../.github/workflows/agentic-e2e.yml) runs the suite on Wednesdays at 03:47 UTC and on demand (`gh workflow run agentic-e2e.yml --ref <branch>`), never on pull requests, in four jobs: the page sweep and everything else, on each viewport. On the 1st of each month at 05:13 UTC it runs a bug bash instead: one exploration per persona, each with its own charter. On demand with a goal, it runs one exploration with the chosen persona and viewport. It uses the `LIVE_OPENROUTER_API_KEY` secret (the `E2E_AGENT_MODEL` and `E2E_JUDGE_MODEL` repository variables override the models), fails when the key is missing, and writes the report to the job summary. Findings never fail the workflow: an exploration that reports issues (`e2e explore` exit 1) passes with a warning annotation, because its findings are leads to reproduce. Any other non-zero exit, or exit 1 with no issue recorded, is the harness or the stack failing, and fails the job. The suite is not a required check: model judgments can flake, and a red weekly run is a finding to triage.
273273

274274
e2e is pre-1.0 and its minor releases have broken APIs, so it is pinned exactly. Read its changelog before upgrading, refresh the vendored skill as its header says, and expect the replay cache to re-record after a minor bump.
275275

‎e2e/full-stack/current-debate.spec.ts‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,8 @@ test("two debates keep their motions, sources, and results apart", async ({ sign
9797
await expect(page.getByTestId("debate-sources")).toContainText("Zoo study");
9898
await expect(page.getByTestId("debate-sources")).not.toContainText("Homework study");
9999
await expect(page.getByTestId("debate-recent")).toContainText(ZOOS);
100+
// Every row is titled with the motion, so the side tells a Pro case from a Con one.
101+
await expect(page.getByTestId("debate-recent")).toContainText("Case · In favor");
100102
await snap(page, "4-zoo-dashboard");
101103

102104
// Switching back switches the Library and History with it.

‎e2e/quality-backlog.spec.ts‎

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,3 +159,22 @@ test('language menu closes once a language is chosen', async ({ page }) => {
159159
await expect(page.getByRole('radiogroup')).toHaveCount(0);
160160
await expect(page.locator('html')).toHaveAttribute('lang', 'hu');
161161
});
162+
163+
// Bug bash 2026-11-01: a motion or argument of only spaces passed validation and spent an AI request.
164+
test('a motion or argument of only spaces counts as blank', async ({ page }) => {
165+
await stubSupabaseSession(page);
166+
const aiCalls: string[] = [];
167+
page.on('request', (request) => { if (request.url().includes('/functions/v1/ai')) aiCalls.push(request.url()); });
168+
169+
await page.goto('/app/generate');
170+
await page.getByLabel('Motion / topic *').fill(' ');
171+
await page.getByRole('button', { name: 'Generate debate case' }).click();
172+
await expect(page.getByText('Motion is required')).toBeVisible();
173+
174+
await page.goto('/app/attack');
175+
await page.getByRole('textbox', { name: /argument/i }).first().fill(' ');
176+
await page.getByRole('button', { name: 'Find weaknesses' }).click();
177+
await expect(page.getByText('Case text is required')).toBeVisible();
178+
179+
expect(aiCalls).toEqual([]);
180+
});

‎packages/core/src/ai/schemas/attack.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { z } from 'zod';
33

44
export const AttackInputSchema = z.object({
55
// A catalog key, not text: the form shows it in the interface language.
6-
caseText: z.string().min(1, 'validation.caseTextRequired'),
6+
caseText: z.string().trim().min(1, 'validation.caseTextRequired'),
77
aggressiveness: z.enum(['mild', 'moderate', 'ruthless']),
88
judgeParadigm: z.enum(['logic', 'values', 'policy']),
99
detectFallacies: z.boolean(),

‎packages/core/src/ai/schemas/brainstorm.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { z } from 'zod';
33

44
export const BrainstormInputSchema = z.object({
55
// A catalog key, not text: the form shows it in the interface language.
6-
motion: z.string().min(1, 'validation.motionRequired'),
6+
motion: z.string().trim().min(1, 'validation.motionRequired'),
77
side: z.enum(['pro', 'con', 'both']),
88
context: z.string().optional(),
99
outputMode: z.enum(['mindmap', 'buckets', 'topfive']),

‎packages/core/src/ai/schemas/generate.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { z } from 'zod';
33

44
export const GenerateInputSchema = z.object({
55
// A catalog key, not text: the form shows it in the interface language.
6-
motion: z.string().min(1, 'validation.motionRequired'),
6+
motion: z.string().trim().min(1, 'validation.motionRequired'),
77
side: z.enum(['pro', 'con']),
88
format: z.enum(['custom', 'bp', 'policy', 'classroom']), // Kept others for backward compatibility but UI will restrict
99
role: z.enum(['intro', 'r1', 'r2', 'conc', 'general']),

‎src/components/features/sparring/SparringRound.tsx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -204,7 +204,7 @@ export function SparringRound({
204204
{!judging && <ClockAnnouncer secondsLeft={session.timerRemainingSeconds} running={session.timerRunning} />}
205205
</div>
206206
</div>
207-
<Progress value={judging ? 0 : timerProgress} className={cn('mt-5 h-2', tone === 'over' ? '[&>div]:bg-destructive' : isUrgent ? '[&>div]:bg-amber-500' : '[&>div]:bg-primary')} aria-label={t('sparring.round.timeRemaining', 'Time remaining')} />
207+
<Progress value={judging ? 0 : timerProgress} className={cn('mt-5 h-2', tone === 'over' ? '[&>div]:bg-destructive' : isUrgent ? '[&>div]:bg-amber-500' : '[&>div]:bg-primary')} aria-label={t('sparring.round.timeRemaining', '{{time}} remaining', { time: formatRoundTime(session.timerRemainingSeconds) })} />
208208
{awaitingStart && (
209209
<div className="mt-4 flex flex-wrap items-center gap-3" data-testid="sparring-ready">
210210
{!voice && (

0 commit comments

Comments
 (0)