/ src / lib / server / tasks / task-validation.test.ts
task-validation.test.ts
  1  import assert from 'node:assert/strict'
  2  import { test } from 'node:test'
  3  import { validateTaskCompletion } from '@/lib/server/tasks/task-validation'
  4  import type { BoardTask } from '@/types'
  5  
  6  test('validateTaskCompletion fails screenshot delivery tasks without artifact evidence', () => {
  7    const validation = validateTaskCompletion({
  8      title: 'Take screenshot and send it every minute',
  9      description: 'Schedule a screenshot capture and deliver it to the user.',
 10      result: 'Existing schedule verified for taking screenshots every minute. Waiting for next run.',
 11      error: null,
 12    } as Partial<BoardTask>)
 13  
 14    assert.equal(validation.ok, false)
 15    assert.ok(validation.reasons.some((reason) => reason.includes('Screenshot delivery task is missing artifact evidence')))
 16  })
 17  
 18  test('validateTaskCompletion accepts screenshot delivery tasks with upload artifact evidence', () => {
 19    const validation = validateTaskCompletion({
 20      title: 'Take screenshot and send it',
 21      description: 'Capture Wikipedia and return the file to the user.',
 22      result: 'Captured and sent screenshot successfully: sandbox:/api/uploads/1234-wikipedia.png',
 23      error: null,
 24    } as Partial<BoardTask>)
 25  
 26    assert.equal(validation.ok, true)
 27  })
 28  
 29  test('validateTaskCompletion accepts concise non-implementation result summaries', () => {
 30    const validation = validateTaskCompletion({
 31      title: 'Answer greeting',
 32      description: 'Respond to a basic hello prompt.',
 33      result: 'Hello! How can I help you today?',
 34      error: null,
 35    } as Partial<BoardTask>)
 36  
 37    assert.equal(validation.ok, true)
 38  })
 39  
 40  test('validateTaskCompletion still enforces stricter minimum for implementation tasks', () => {
 41    const validation = validateTaskCompletion({
 42      title: 'Fix retry bug',
 43      description: 'Implement queue retry fixes and verify.',
 44      result: 'Patched queue retry bug.',
 45      error: null,
 46    } as Partial<BoardTask>)
 47  
 48    assert.equal(validation.ok, false)
 49    assert.ok(validation.reasons.some((reason) => reason.includes('Result summary is too short')))
 50  })
 51  
 52  test('validateTaskCompletion fails implementation task with unfinished next-step language', () => {
 53    const validation = validateTaskCompletion({
 54      title: 'Build weather dashboard',
 55      description: 'Implement dashboard and run dev server.',
 56      result: 'I prepared an outline. Next I will run the server once access is granted.',
 57      error: null,
 58    } as Partial<BoardTask>)
 59  
 60    assert.equal(validation.ok, false)
 61    assert.ok(validation.reasons.some((reason) => reason.includes('unfinished work')))
 62  })
 63  
 64  test('validateTaskCompletion fails implementation task that requests shell access', () => {
 65    const validation = validateTaskCompletion({
 66      title: 'Create blog and run server',
 67      description: 'Create markdown blog and serve it.',
 68      result: 'I created the blog file at data/workspace/blog/swarmclaw-blog.md, but I need access to the shell to proceed. Once the access is granted, I will finish setup.',
 69      error: null,
 70    } as Partial<BoardTask>)
 71  
 72    assert.equal(validation.ok, false)
 73    assert.ok(validation.reasons.some((reason) => reason.includes('unfinished work')))
 74  })
 75  
 76  test('validateTaskCompletion fails untitled tasks with empty metadata', () => {
 77    const validation = validateTaskCompletion({
 78      title: 'Untitled Task',
 79      description: '',
 80      result: 'Could you provide more information about what you need?',
 81      error: null,
 82    } as Partial<BoardTask>)
 83  
 84    assert.equal(validation.ok, false)
 85    assert.ok(validation.reasons.some((reason) => reason.includes('metadata is too vague')))
 86  })
 87  
 88  test('validateTaskCompletion enforces explicit quality gate evidence requirements', () => {
 89    const validation = validateTaskCompletion({
 90      title: 'Ship API migration summary',
 91      description: 'Summarize the migration outcome.',
 92      result: 'Migration summary completed successfully with no extra artifacts included.',
 93      qualityGate: {
 94        enabled: true,
 95        minResultChars: 20,
 96        minEvidenceItems: 2,
 97        requireArtifact: true,
 98      },
 99      error: null,
100    } as Partial<BoardTask>)
101  
102    assert.equal(validation.ok, false)
103    assert.ok(validation.reasons.some((reason) => reason.includes('insufficient completion evidence')))
104    assert.ok(validation.reasons.some((reason) => reason.includes('artifact evidence is required')))
105  })
106  
107  test('validateTaskCompletion accepts short replies when prompt explicitly asks for one', () => {
108    const cases = [
109      { description: 'Reply with the word PONG only.', result: 'PONG' },
110      { description: 'Answer in one word.', result: 'Yes' },
111      { description: 'Yes or no: is it raining?', result: 'No' },
112      { description: 'Just say hi.', result: 'hi' },
113      { description: 'Respond with the number 42 only.', result: '42' },
114    ]
115    for (const c of cases) {
116      const validation = validateTaskCompletion({
117        title: 'Ping',
118        description: c.description,
119        result: c.result,
120        error: null,
121      } as Partial<BoardTask>)
122      assert.equal(validation.ok, true, `expected ok for "${c.description}" -> "${c.result}", got: ${validation.reasons.join('; ')}`)
123    }
124  })
125  
126  test('validateTaskCompletion still rejects short generic replies when no short-answer signal in prompt', () => {
127    const validation = validateTaskCompletion({
128      title: 'Summarize the project',
129      description: 'Give an overview of the codebase.',
130      result: 'Done.',
131      error: null,
132    } as Partial<BoardTask>)
133    assert.equal(validation.ok, false)
134    assert.ok(validation.reasons.some((r) => r.includes('Result summary is too short')))
135  })
136  
137  test('validateTaskCompletion passes explicit quality gate when evidence checks are met', () => {
138    const validation = validateTaskCompletion({
139      title: 'Ship API migration summary',
140      description: 'Summarize the migration outcome.',
141      result: 'Ran npm test and tests passed. Updated src/api/migrate.ts. Uploaded evidence: sandbox:/api/uploads/migration-proof.png.',
142      artifacts: [{
143        url: 'sandbox:/api/uploads/migration-proof.png',
144        type: 'image',
145        filename: 'migration-proof.png',
146      }],
147      qualityGate: {
148        enabled: true,
149        minResultChars: 20,
150        minEvidenceItems: 2,
151        requireArtifact: true,
152        requireVerification: true,
153      },
154      error: null,
155    } as Partial<BoardTask>)
156  
157    assert.equal(validation.ok, true)
158  })