[beadboard] Add Dispatch-to-Agent button and prompt template
## Context
We want a one-click path from the right-panel task detail into the
claude-agent-service runner, without the user copy-pasting the bead id
into a CLI or another tab. The runner expects a self-contained prompt
that restates the bead id, title, description, acceptance criteria, and
the guard rails the agent must operate under (no push, no file edits,
no terraform/kubectl/helm). The prompt template lives in `src/lib/` so
it can be tested and reused from the server-side dispatch route.
The right-panel button needs to:
- Only appear when the bead is actionable (`open` or `in_progress`).
- Disable itself while the claude-agent-service is already busy (the
service has a global `asyncio.Lock` — parallel dispatches 409).
- Disable itself when the bead lacks acceptance criteria. An agent that
doesn't know what "done" looks like burns budget and closes nothing.
- Surface the resulting `job_id` or any 409/error back to the user.
The project has no toast library (no `sonner`, no `react-hot-toast`), so
we render status inline under the button rather than pulling in a new
dependency for this single surface.
## This change
- `src/lib/dispatch-prompt.ts` exports `buildDispatchPrompt(bead)` which
produces the exact prompt the agent runner expects. Bead id, priority
(`P<n>`), issue type, description, and acceptance criteria are
interpolated; `<job_id>` stays a literal placeholder because the agent
only learns its own id at runtime (env var).
- `src/components/shared/dispatch-button.tsx` is a focused client
component with three responsibilities:
1. Poll `GET /api/agent-status` every 5 s while the panel is open
(plus an initial fetch on mount), mirror `busy` into local state.
2. On click, `POST /api/agent-dispatch` with `{taskId}`; branch on
200 / 409 / other.
3. Render an inline status line under the button (`text-xs`, tone
driven by `ok | info | error`) — no toast dep required.
The poll interval self-clears on unmount so closing the panel stops
network traffic.
- `src/components/shared/thread-drawer.tsx` renders `<DispatchButton>`
alongside the existing "Edit task" button in the summary section,
wrapped in a `flex-wrap` so the two controls reflow on narrow panes.
- Registers two new tests in `package.json`'s enumerated test script.
## What is NOT in this change
- The `/api/agent-dispatch` and `/api/agent-status` routes themselves —
those land in the next commit. The button calls them but the server
side is intentionally a separate step so each commit can be reviewed
in isolation.
- No real toast system is introduced; inline status is sufficient.
- No change to how task state transitions on dispatch. The agent itself
is expected to run `bd update --claim` / `bd close` via the prompt's
operating rules.
## Test Plan
### Automated
```
$ node --import tsx --test tests/lib/dispatch-prompt.test.ts \
tests/components/shared/dispatch-button.test.tsx
# tests 7 pass 7 fail 0
```
Covers:
- Bead id appears in opening paragraph and in both `bd note` / `bd close`
commands.
- Priority rendered as `P<n>`, issue type echoed.
- Description and acceptance criteria quoted verbatim when present.
- `(no description)` / `(no acceptance criteria)` fallbacks when null.
- Guard rails block present (no terraform/kubectl/helm, workspace bd
path, `bd update … --status blocked` fallback).
- DispatchButton module loads and exports both named and default.
`npm run typecheck` shows only the pre-existing `OrchestratorChatMessage`
type gap in `left-panel.tsx` that reproduces on untouched `main`.
### Manual Verification
1. `npm install`
2. `npm run dev`
3. Open `http://localhost:3000/?task=<some-open-bead-id>`
4. Expected: "Dispatch to Agent" button next to "Edit task" in the
right-panel summary section.
5. Button disabled on beads with `status in {closed, blocked, deferred}`
(they don't render the button at all).
6. Button disabled on beads missing acceptance criteria, with tooltip
"Task is missing acceptance criteria — cannot dispatch.".
7. Click: UI flips to "Dispatching…"; once the next commit is merged,
the agent-dispatch route will surface a `job_id` (today it returns
404 which renders as "Dispatch failed (HTTP 404)").
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
845e90d7c0
commit
394a771b67
6 changed files with 245 additions and 2 deletions
|
|
@ -13,7 +13,7 @@
|
|||
"start": "next start",
|
||||
"lint": "eslint .",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"test": "node --test tests/bootstrap.test.mjs && node --import tsx --test tests/pr14-critical-bugs.test.ts && node --import tsx --test tests/components/shared/base-card.test.tsx && node --import tsx --test tests/components/shared/agent-avatar.test.tsx && node --import tsx --test tests/components/sessions/sessions-header.test.ts && node --import tsx --test tests/components/sessions/agent-station-logic.test.ts && node --import tsx --test tests/lib/parser.test.ts && node --import tsx --test tests/lib/pathing.test.ts && node --import tsx --test tests/components/shared/left-panel.test.tsx && node --import tsx --test tests/components/shared/top-bar.test.tsx && node --import tsx --test tests/components/shared/mobile-nav.test.tsx && node --import tsx --test tests/components/swarm/swarm-card.test.tsx && node --import tsx --test tests/hooks/url-state-integration.test.ts && node --import tsx --test tests/hooks/use-graph-analysis.test.ts && node --import tsx --test tests/components/graph/smart-dag.test.tsx && node --import tsx --test tests/components/unified-shell.test.tsx && node --import tsx --test tests/components/blocked-triage-modal.test.tsx && node --import tsx --test tests/components/graph/graph-node-labels.test.tsx && node --import tsx --test tests/components/graph/graph-node-assign.test.tsx && node --import tsx --test tests/components/graph/graph-node-conversation.test.tsx && node --import tsx --test tests/lib/coord-schema.test.ts && node --import tsx --test tests/lib/install-manifest.test.ts && node --import tsx --test tests/lib/runtime-manager.test.ts && node --import tsx --test tests/lib/coord-events.test.ts && node --import tsx --test tests/api/coord-events-route.test.ts && node --import tsx --test tests/lib/coord-projections-inbox.test.ts && node --import tsx --test tests/lib/coord-projections-reservations.test.ts && node --import tsx --test tests/components/sessions/conversation-drawer-coord.test.tsx && node --import tsx --test tests/scripts/beadboard-launcher.test.ts && node --import tsx --test tests/scripts/beadboard-launcher-runtime.test.ts && node --import tsx --test tests/scripts/beadboard-launcher-status-text.test.ts && node --import tsx --test tests/scripts/install-wrappers-contract.test.ts && node --import tsx --test tests/scripts/install-sh-smoke.test.ts && node --import tsx --test tests/scripts/install-legacy-migration.test.ts && node --import tsx --test tests/scripts/installer-ci-contract.test.ts && node --import tsx --test tests/docs/installer-quickstart-contract.test.ts && node --import tsx --test tests/docs/runtime-manager-adr-contract.test.ts && node --import tsx --test tests/cli/beadboard-cli.test.ts && node --import tsx --test tests/cli/beadboard-bin-routing.test.ts && node --import tsx --test tests/cli/beadboard-help-output.test.ts && node --import tsx --test tests/skills/beadboard-driver/resolve-bb.test.ts && node --import tsx --test tests/skills/beadboard-driver/session-preflight.test.ts && node --import tsx --test tests/skills/beadboard-driver/generate-agent-name.test.ts && node --import tsx --test tests/skills/beadboard-driver/readiness-report.test.ts && node --import tsx --test tests/skills/beadboard-driver/skill-local-runner.test.ts && node --import tsx --test tests/skills/beadboard-driver/diagnose-env.test.ts && node --import tsx --test tests/skills/beadboard-driver/heal-common-issues.test.ts && node --import tsx --test tests/lib/epic-graph.test.ts && node --import tsx --test tests/components/shared/left-panel-filtering.test.ts && node --import tsx --test tests/hooks/use-beads-subscription-contract.test.ts && node --import tsx --test tests/components/graph/dependency-graph-hide-closed-contract.test.ts && node --import tsx --test tests/components/shared/unified-shell-hide-closed-contract.test.ts && node --import tsx --test tests/lib/agent-registry.test.ts && node --import tsx --test tests/api/agents-mail.test.ts && node --import tsx --test tests/skills/beadboard-driver/bb-mail-shim.test.ts && node --import tsx --test tests/skills/beadboard-driver/ensure-bb-mail-configured.test.ts && node skills/beadboard-driver/tests/run-tests.mjs",
|
||||
"test": "node --test tests/bootstrap.test.mjs && node --import tsx --test tests/pr14-critical-bugs.test.ts && node --import tsx --test tests/components/shared/base-card.test.tsx && node --import tsx --test tests/components/shared/agent-avatar.test.tsx && node --import tsx --test tests/components/sessions/sessions-header.test.ts && node --import tsx --test tests/components/sessions/agent-station-logic.test.ts && node --import tsx --test tests/lib/parser.test.ts && node --import tsx --test tests/lib/pathing.test.ts && node --import tsx --test tests/components/shared/left-panel.test.tsx && node --import tsx --test tests/components/shared/top-bar.test.tsx && node --import tsx --test tests/components/shared/mobile-nav.test.tsx && node --import tsx --test tests/components/swarm/swarm-card.test.tsx && node --import tsx --test tests/hooks/url-state-integration.test.ts && node --import tsx --test tests/hooks/use-graph-analysis.test.ts && node --import tsx --test tests/components/graph/smart-dag.test.tsx && node --import tsx --test tests/components/unified-shell.test.tsx && node --import tsx --test tests/components/blocked-triage-modal.test.tsx && node --import tsx --test tests/components/graph/graph-node-labels.test.tsx && node --import tsx --test tests/components/graph/graph-node-assign.test.tsx && node --import tsx --test tests/components/graph/graph-node-conversation.test.tsx && node --import tsx --test tests/lib/coord-schema.test.ts && node --import tsx --test tests/lib/install-manifest.test.ts && node --import tsx --test tests/lib/runtime-manager.test.ts && node --import tsx --test tests/lib/coord-events.test.ts && node --import tsx --test tests/api/coord-events-route.test.ts && node --import tsx --test tests/lib/coord-projections-inbox.test.ts && node --import tsx --test tests/lib/coord-projections-reservations.test.ts && node --import tsx --test tests/components/sessions/conversation-drawer-coord.test.tsx && node --import tsx --test tests/scripts/beadboard-launcher.test.ts && node --import tsx --test tests/scripts/beadboard-launcher-runtime.test.ts && node --import tsx --test tests/scripts/beadboard-launcher-status-text.test.ts && node --import tsx --test tests/scripts/install-wrappers-contract.test.ts && node --import tsx --test tests/scripts/install-sh-smoke.test.ts && node --import tsx --test tests/scripts/install-legacy-migration.test.ts && node --import tsx --test tests/scripts/installer-ci-contract.test.ts && node --import tsx --test tests/docs/installer-quickstart-contract.test.ts && node --import tsx --test tests/docs/runtime-manager-adr-contract.test.ts && node --import tsx --test tests/cli/beadboard-cli.test.ts && node --import tsx --test tests/cli/beadboard-bin-routing.test.ts && node --import tsx --test tests/cli/beadboard-help-output.test.ts && node --import tsx --test tests/skills/beadboard-driver/resolve-bb.test.ts && node --import tsx --test tests/skills/beadboard-driver/session-preflight.test.ts && node --import tsx --test tests/skills/beadboard-driver/generate-agent-name.test.ts && node --import tsx --test tests/skills/beadboard-driver/readiness-report.test.ts && node --import tsx --test tests/skills/beadboard-driver/skill-local-runner.test.ts && node --import tsx --test tests/skills/beadboard-driver/diagnose-env.test.ts && node --import tsx --test tests/skills/beadboard-driver/heal-common-issues.test.ts && node --import tsx --test tests/lib/epic-graph.test.ts && node --import tsx --test tests/components/shared/left-panel-filtering.test.ts && node --import tsx --test tests/hooks/use-beads-subscription-contract.test.ts && node --import tsx --test tests/components/graph/dependency-graph-hide-closed-contract.test.ts && node --import tsx --test tests/components/shared/unified-shell-hide-closed-contract.test.ts && node --import tsx --test tests/lib/agent-registry.test.ts && node --import tsx --test tests/api/agents-mail.test.ts && node --import tsx --test tests/skills/beadboard-driver/bb-mail-shim.test.ts && node --import tsx --test tests/skills/beadboard-driver/ensure-bb-mail-configured.test.ts && node --import tsx --test tests/lib/dispatch-prompt.test.ts && node --import tsx --test tests/components/shared/dispatch-button.test.tsx && node skills/beadboard-driver/tests/run-tests.mjs",
|
||||
"video": "remotion preview src/video/index.ts",
|
||||
"video:render": "remotion render src/video/index.ts Main out/video.mp4",
|
||||
"video:thumbnail": "remotion still src/video/index.ts Main out/thumbnail.png --frame=60"
|
||||
|
|
|
|||
126
src/components/shared/dispatch-button.tsx
Normal file
126
src/components/shared/dispatch-button.tsx
Normal file
|
|
@ -0,0 +1,126 @@
|
|||
'use client';
|
||||
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { Rocket } from 'lucide-react';
|
||||
|
||||
import { Button } from '@/components/ui/button';
|
||||
|
||||
import type { BeadIssue } from '../../lib/types';
|
||||
|
||||
interface DispatchButtonProps {
|
||||
bead: BeadIssue;
|
||||
}
|
||||
|
||||
type StatusMessage = { kind: 'info' | 'ok' | 'error'; text: string };
|
||||
|
||||
const DISPATCHABLE_STATUSES = new Set<BeadIssue['status']>(['open', 'in_progress']);
|
||||
const POLL_INTERVAL_MS = 5000;
|
||||
|
||||
export function DispatchButton({ bead }: DispatchButtonProps) {
|
||||
const [agentBusy, setAgentBusy] = useState<boolean>(false);
|
||||
const [submitting, setSubmitting] = useState<boolean>(false);
|
||||
const [status, setStatus] = useState<StatusMessage | null>(null);
|
||||
const mountedRef = useRef<boolean>(true);
|
||||
|
||||
const isDispatchable = DISPATCHABLE_STATUSES.has(bead.status);
|
||||
const hasAcceptance = Boolean(bead.acceptance_criteria?.trim());
|
||||
|
||||
const fetchAgentStatus = useCallback(async () => {
|
||||
try {
|
||||
const response = await fetch('/api/agent-status', { cache: 'no-store' });
|
||||
if (!response.ok) return;
|
||||
const payload = await response.json() as { busy?: boolean };
|
||||
if (mountedRef.current) {
|
||||
setAgentBusy(Boolean(payload.busy));
|
||||
}
|
||||
} catch {
|
||||
// Leave previous state; fail closed is not required for a heartbeat.
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
mountedRef.current = true;
|
||||
if (!isDispatchable) {
|
||||
return () => {
|
||||
mountedRef.current = false;
|
||||
};
|
||||
}
|
||||
|
||||
void fetchAgentStatus();
|
||||
const handle = window.setInterval(() => { void fetchAgentStatus(); }, POLL_INTERVAL_MS);
|
||||
return () => {
|
||||
mountedRef.current = false;
|
||||
window.clearInterval(handle);
|
||||
};
|
||||
}, [isDispatchable, fetchAgentStatus]);
|
||||
|
||||
const handleDispatch = useCallback(async () => {
|
||||
setSubmitting(true);
|
||||
setStatus({ kind: 'info', text: 'Submitting…' });
|
||||
try {
|
||||
const response = await fetch('/api/agent-dispatch', {
|
||||
method: 'POST',
|
||||
headers: { 'content-type': 'application/json' },
|
||||
body: JSON.stringify({ taskId: bead.id }),
|
||||
});
|
||||
const payload = await response.json().catch(() => ({} as Record<string, unknown>));
|
||||
if (response.status === 409) {
|
||||
setStatus({ kind: 'error', text: 'Agent busy — try again shortly' });
|
||||
setAgentBusy(true);
|
||||
return;
|
||||
}
|
||||
if (!response.ok) {
|
||||
const message = typeof payload.error === 'string' ? payload.error : `Dispatch failed (HTTP ${response.status})`;
|
||||
setStatus({ kind: 'error', text: message });
|
||||
return;
|
||||
}
|
||||
const jobId = typeof payload.job_id === 'string' ? payload.job_id : 'unknown';
|
||||
setStatus({ kind: 'ok', text: `Job \`${jobId}\` submitted` });
|
||||
setAgentBusy(true);
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : 'Network error';
|
||||
setStatus({ kind: 'error', text: message });
|
||||
} finally {
|
||||
setSubmitting(false);
|
||||
}
|
||||
}, [bead.id]);
|
||||
|
||||
if (!isDispatchable) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const disabled = submitting || agentBusy || !hasAcceptance;
|
||||
const hint = !hasAcceptance
|
||||
? 'Task is missing acceptance criteria — cannot dispatch.'
|
||||
: agentBusy
|
||||
? 'Agent is currently busy.'
|
||||
: 'Hand this task to the claude-agent-service runner.';
|
||||
|
||||
const statusToneClass = status?.kind === 'ok'
|
||||
? 'text-[var(--ui-accent-ready)]'
|
||||
: status?.kind === 'error'
|
||||
? 'text-[#EAA7A0]'
|
||||
: 'text-[var(--ui-text-muted)]';
|
||||
|
||||
return (
|
||||
<div className="pt-1" data-testid="dispatch-button-container">
|
||||
<Button
|
||||
onClick={() => void handleDispatch()}
|
||||
disabled={disabled}
|
||||
title={hint}
|
||||
className="h-8 rounded-full bg-[var(--ui-accent-info)] px-4 text-[#0b1e30] hover:bg-[color-mix(in_srgb,var(--ui-accent-info)_86%,white)] disabled:opacity-40"
|
||||
data-testid="dispatch-agent-button"
|
||||
>
|
||||
<Rocket className="mr-2 h-3.5 w-3.5" />
|
||||
{submitting ? 'Dispatching…' : 'Dispatch to Agent'}
|
||||
</Button>
|
||||
{status ? (
|
||||
<p className={`mt-1 text-xs ${statusToneClass}`} data-testid="dispatch-status">
|
||||
{status.text}
|
||||
</p>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default DispatchButton;
|
||||
|
|
@ -13,6 +13,7 @@ import type { UpdateMutationPayload } from '../../lib/mutations';
|
|||
import type { BeadIssue } from '../../lib/types';
|
||||
import { ThreadView, type ThreadItem } from './thread-view';
|
||||
import { useResponsive } from '../../hooks/use-responsive';
|
||||
import { DispatchButton } from './dispatch-button';
|
||||
|
||||
interface ThreadDrawerProps {
|
||||
isOpen: boolean;
|
||||
|
|
@ -284,7 +285,7 @@ export function ThreadDrawer({
|
|||
<Badge className="rounded-full border border-[var(--ui-border-soft)] bg-[var(--ui-bg-panel)] text-[var(--ui-text-muted)]">{issue.issue_type}</Badge>
|
||||
{issue.assignee ? <Badge className="rounded-full border border-[var(--ui-border-soft)] bg-[var(--ui-bg-panel)] text-[var(--ui-text-muted)]">@{issue.assignee}</Badge> : null}
|
||||
</div>
|
||||
<div className="pt-1">
|
||||
<div className="flex flex-wrap items-start gap-2 pt-1">
|
||||
<Button
|
||||
onClick={() => setEditMode(true)}
|
||||
disabled={!canEdit}
|
||||
|
|
@ -292,6 +293,7 @@ export function ThreadDrawer({
|
|||
>
|
||||
<Edit3 className="mr-2 h-3.5 w-3.5" /> Edit task
|
||||
</Button>
|
||||
<DispatchButton bead={issue} />
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
|
|
|
|||
25
src/lib/dispatch-prompt.ts
Normal file
25
src/lib/dispatch-prompt.ts
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
import type { BeadIssue } from './types';
|
||||
|
||||
export function buildDispatchPrompt(bead: BeadIssue): string {
|
||||
const priority = `P${bead.priority}`;
|
||||
const description = bead.description?.trim() ?? '(no description)';
|
||||
const acceptance = bead.acceptance_criteria?.trim() ?? '(no acceptance criteria)';
|
||||
|
||||
return [
|
||||
`You are the "beads-task-runner" agent picking up beads task \`${bead.id}\`.`,
|
||||
``,
|
||||
`Task: ${bead.title} (${priority}, type=${bead.issue_type})`,
|
||||
`Description:`,
|
||||
description,
|
||||
``,
|
||||
`Acceptance criteria:`,
|
||||
acceptance,
|
||||
``,
|
||||
`Operating rules:`,
|
||||
`- Always use \`bd --db /workspace/.beads …\` for every bd call.`,
|
||||
`- First action: \`bd note ${bead.id} "claimed by agent <job_id>"\`.`,
|
||||
`- Do NOT push, do NOT edit files, do NOT run terraform/kubectl/helm.`,
|
||||
`- If the task is outside those rails, run \`bd update ${bead.id} --status blocked\` with a note and stop.`,
|
||||
`- On success: \`bd close ${bead.id} -r "completed by agent <job_id>"\`.`,
|
||||
].join('\n');
|
||||
}
|
||||
14
tests/components/shared/dispatch-button.test.tsx
Normal file
14
tests/components/shared/dispatch-button.test.tsx
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
|
||||
test('DispatchButton module exports component', async () => {
|
||||
const mod = await import('../../../src/components/shared/dispatch-button');
|
||||
assert.ok(mod.DispatchButton, 'DispatchButton should be exported');
|
||||
assert.equal(typeof mod.DispatchButton, 'function', 'DispatchButton should be a function');
|
||||
});
|
||||
|
||||
test('DispatchButton module exports default', async () => {
|
||||
const mod = await import('../../../src/components/shared/dispatch-button');
|
||||
assert.ok(mod.default, 'default export should exist');
|
||||
assert.equal(mod.default, mod.DispatchButton, 'default should match named export');
|
||||
});
|
||||
76
tests/lib/dispatch-prompt.test.ts
Normal file
76
tests/lib/dispatch-prompt.test.ts
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
|
||||
import { buildDispatchPrompt } from '../../src/lib/dispatch-prompt';
|
||||
import type { BeadIssue } from '../../src/lib/types';
|
||||
|
||||
function makeIssue(overrides: Partial<BeadIssue> = {}): BeadIssue {
|
||||
return {
|
||||
id: 'beadboard-abc.1',
|
||||
title: 'Sample task',
|
||||
description: 'Describe the work in detail.',
|
||||
status: 'open',
|
||||
priority: 1,
|
||||
issue_type: 'task',
|
||||
assignee: null,
|
||||
templateId: null,
|
||||
owner: null,
|
||||
labels: [],
|
||||
dependencies: [],
|
||||
created_at: '2026-01-01T00:00:00Z',
|
||||
updated_at: '2026-01-01T00:00:00Z',
|
||||
closed_at: null,
|
||||
close_reason: null,
|
||||
closed_by_session: null,
|
||||
created_by: null,
|
||||
due_at: null,
|
||||
estimated_minutes: null,
|
||||
external_ref: null,
|
||||
acceptance_criteria: 'Checkbox: done when X.',
|
||||
metadata: {},
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
test('buildDispatchPrompt includes bead id in opening and close commands', () => {
|
||||
const prompt = buildDispatchPrompt(makeIssue({ id: 'beadboard-77.3' }));
|
||||
|
||||
assert.match(prompt, /beads-task-runner/);
|
||||
assert.match(prompt, /`beadboard-77\.3`/);
|
||||
assert.match(prompt, /bd note beadboard-77\.3 "claimed by agent <job_id>"/);
|
||||
assert.match(prompt, /bd close beadboard-77\.3 -r "completed by agent <job_id>"/);
|
||||
});
|
||||
|
||||
test('buildDispatchPrompt renders priority as P<n> and includes issue type', () => {
|
||||
const prompt = buildDispatchPrompt(makeIssue({ priority: 2, issue_type: 'bug' }));
|
||||
|
||||
assert.match(prompt, /\(P2, type=bug\)/);
|
||||
});
|
||||
|
||||
test('buildDispatchPrompt includes description and acceptance criteria verbatim', () => {
|
||||
const prompt = buildDispatchPrompt(makeIssue({
|
||||
description: 'Concrete description here.',
|
||||
acceptance_criteria: 'Concrete acceptance here.',
|
||||
}));
|
||||
|
||||
assert.ok(prompt.includes('Concrete description here.'));
|
||||
assert.ok(prompt.includes('Concrete acceptance here.'));
|
||||
});
|
||||
|
||||
test('buildDispatchPrompt falls back when description or acceptance are missing', () => {
|
||||
const prompt = buildDispatchPrompt(makeIssue({
|
||||
description: null,
|
||||
acceptance_criteria: null,
|
||||
}));
|
||||
|
||||
assert.ok(prompt.includes('(no description)'));
|
||||
assert.ok(prompt.includes('(no acceptance criteria)'));
|
||||
});
|
||||
|
||||
test('buildDispatchPrompt includes operating guard rails', () => {
|
||||
const prompt = buildDispatchPrompt(makeIssue());
|
||||
|
||||
assert.match(prompt, /bd --db \/workspace\/\.beads/);
|
||||
assert.match(prompt, /Do NOT push, do NOT edit files, do NOT run terraform\/kubectl\/helm\./);
|
||||
assert.match(prompt, /bd update beadboard-abc\.1 --status blocked/);
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue