Symptoom: IDEA_GRILL en IDEA_MAKE_PLAN jobs hingen 11+ minuten zonder
update_job_status aan te roepen. Claude zag in de prompt:
- "Je bent een grill-agent voor Scrum4Me-idee {idea_code}" — letterlijke
string omdat run-one-job.ts alleen $PAYLOAD_PATH substitueert, geen
{idea_*}-vars.
- "context (meegegeven in wait_for_job-payload)" — maar Claude krijgt geen
wait_for_job-respons, want die tool zit niet meer in allowed_tools voor
idea-kinds (de runner claimt al).
- Geen instructie om $PAYLOAD_PATH te lezen — de placeholder ontbrak in
beide idea-prompts (alleen task/sprint/plan-chat hadden 'm).
Resultaat: Claude wist niet wat het te doen had, kon geen idea_id of
job_id achterhalen, en draaide tot de natuurlijke session-cap zonder
ooit de juiste tools aan te roepen.
Fix:
- grill.md en make-plan.md: vervang `wait_for_job`-references door
`scrum4me-docker/bin/run-one-job.ts` (de daadwerkelijke runner).
- Beide prompts beginnen nu met "Lees $PAYLOAD_PATH met de Read-tool"
als verplichte eerste actie. Lijst van velden die uit de payload moeten
worden bewaard (idea.id, idea.code, job_id, product.id, etc.).
- {idea_code} / {idea_title} placeholders verwijderd — alle benodigde
velden komen uit de payload, geen runner-side substitution meer nodig.
- Update_job_status-stap expliciet als "verplicht, ook bij failure".
Tests: kind-prompts.test.ts uitgebreid:
- Alle 5 kinds moeten $PAYLOAD_PATH bevatten (was alleen task/sprint/
plan-chat).
- IDEA_GRILL en IDEA_MAKE_PLAN mogen geen wait_for_job meer noemen.
- IDEA_GRILL en IDEA_MAKE_PLAN mogen geen {idea_*} placeholders meer
bevatten.
19 tests in kind-prompts.test.ts passed (was 13).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
64 lines
2 KiB
TypeScript
64 lines
2 KiB
TypeScript
import { describe, it, expect } from 'vitest'
|
|
import type { ClaudeJobKind } from '@prisma/client'
|
|
import { getKindPromptText, getIdeaPromptText } from '../src/lib/kind-prompts.js'
|
|
|
|
const KINDS: ClaudeJobKind[] = [
|
|
'IDEA_GRILL',
|
|
'IDEA_MAKE_PLAN',
|
|
'TASK_IMPLEMENTATION',
|
|
'SPRINT_IMPLEMENTATION',
|
|
'PLAN_CHAT',
|
|
]
|
|
|
|
describe('getKindPromptText', () => {
|
|
it.each(KINDS)('returnt non-empty content voor %s', (kind) => {
|
|
const text = getKindPromptText(kind)
|
|
expect(text.length).toBeGreaterThan(0)
|
|
})
|
|
|
|
it('TASK_IMPLEMENTATION-prompt verbiedt wait_for_job', () => {
|
|
const text = getKindPromptText('TASK_IMPLEMENTATION')
|
|
expect(text).toMatch(/GEEN.*wait_for_job/)
|
|
})
|
|
|
|
it('SPRINT_IMPLEMENTATION-prompt verbiedt job_heartbeat', () => {
|
|
const text = getKindPromptText('SPRINT_IMPLEMENTATION')
|
|
expect(text).toMatch(/GEEN.*job_heartbeat/)
|
|
})
|
|
|
|
it.each(KINDS)(
|
|
'%s-prompt noemt $PAYLOAD_PATH als variabele (alle kinds — runner doet substitution)',
|
|
(kind) => {
|
|
const text = getKindPromptText(kind)
|
|
expect(text).toContain('$PAYLOAD_PATH')
|
|
},
|
|
)
|
|
|
|
it.each(['IDEA_GRILL', 'IDEA_MAKE_PLAN'] as const)(
|
|
'%s-prompt verwijst niet meer naar wait_for_job (refactor: runner claimt)',
|
|
(kind) => {
|
|
const text = getKindPromptText(kind)
|
|
expect(text).not.toContain('wait_for_job')
|
|
},
|
|
)
|
|
|
|
it.each(['IDEA_GRILL', 'IDEA_MAKE_PLAN'] as const)(
|
|
'%s-prompt bevat geen onvervangen {idea_*} placeholders',
|
|
(kind) => {
|
|
const text = getKindPromptText(kind)
|
|
expect(text).not.toMatch(/\{idea_code\}|\{idea_title\}/)
|
|
},
|
|
)
|
|
})
|
|
|
|
describe('getIdeaPromptText (back-compat)', () => {
|
|
it('returnt content voor IDEA_GRILL', () => {
|
|
expect(getIdeaPromptText('IDEA_GRILL').length).toBeGreaterThan(0)
|
|
})
|
|
it('returnt content voor IDEA_MAKE_PLAN', () => {
|
|
expect(getIdeaPromptText('IDEA_MAKE_PLAN').length).toBeGreaterThan(0)
|
|
})
|
|
it('returnt empty string voor non-idea kind', () => {
|
|
expect(getIdeaPromptText('TASK_IMPLEMENTATION')).toBe('')
|
|
})
|
|
})
|