@trygentic/agentloop 0.15.0-alpha.11 → 0.15.1-alpha.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (25) hide show
  1. package/package.json +3 -3
  2. package/templates/agents/chat/chat.bt.json +346 -0
  3. package/templates/agents/chat/chat.md +84 -0
  4. package/templates/agents/engineer/engineer.bt.json +601 -0
  5. package/templates/agents/engineer/engineer.md +307 -0
  6. package/templates/agents/orchestrator/orchestrator.bt.json +28 -0
  7. package/templates/agents/orchestrator/orchestrator.md +544 -0
  8. package/templates/agents/product-manager/product-manager.bt.json +90 -0
  9. package/templates/agents/product-manager/product-manager.md +207 -0
  10. package/templates/agents/qa-tester/qa-tester.bt.json +677 -0
  11. package/templates/agents/qa-tester/qa-tester.md +143 -0
  12. package/templates/examples/README.md +86 -0
  13. package/templates/examples/engineer.md.example +248 -0
  14. package/templates/examples/example-custom-agent.md.example +158 -0
  15. package/templates/examples/example-plugin.js.example +277 -0
  16. package/templates/non-core-templates/container.md +173 -0
  17. package/templates/non-core-templates/dag-planner.md +96 -0
  18. package/templates/non-core-templates/internal/cli-tester.md +218 -0
  19. package/templates/non-core-templates/internal/qa-tester.md +300 -0
  20. package/templates/non-core-templates/internal/tui-designer.md +370 -0
  21. package/templates/non-core-templates/internal/tui-tester.md +125 -0
  22. package/templates/non-core-templates/maestro-qa.md +240 -0
  23. package/templates/non-core-templates/merge-resolver.md +150 -0
  24. package/templates/non-core-templates/project-detection.md +75 -0
  25. package/templates/non-core-templates/questionnaire.md +124 -0
@@ -0,0 +1,677 @@
1
+ {
2
+ "name": "qa-tester-continuous-agent-tree",
3
+ "description": "Continuous behavior tree for the QA Tester agent. Loops forever, waiting for task assignments from the orchestrator. Handles test execution, environment issues, and provides feedback to engineers without process restarts.",
4
+ "version": "1.0.0",
5
+ "tree": {
6
+ "type": "root",
7
+ "child": {
8
+ "type": "sequence",
9
+ "comment": "Main continuous loop - never exits unless agent is stopped",
10
+ "children": [
11
+ {
12
+ "type": "action",
13
+ "call": "WaitForTask",
14
+ "comment": "Block until orchestrator assigns a task via ContinuousAgentRunner.assignTask()"
15
+ },
16
+ {
17
+ "type": "action",
18
+ "call": "FetchTaskContext",
19
+ "comment": "Load task details, comments, and engineer completion info"
20
+ },
21
+ {
22
+ "type": "selector",
23
+ "comment": "Check for incoming agent messages (non-critical: continue even if unavailable)",
24
+ "children": [
25
+ {
26
+ "type": "action",
27
+ "call": "CheckIncomingMessages",
28
+ "comment": "Poll for messages from other agents (coordination, queries, notifications)"
29
+ },
30
+ {
31
+ "type": "action",
32
+ "call": "NoOp",
33
+ "comment": "Continue without message checking if messaging is unavailable"
34
+ }
35
+ ]
36
+ },
37
+ {
38
+ "type": "selector",
39
+ "comment": "Notify other agents that QA review is starting (non-critical: continue even if unavailable)",
40
+ "children": [
41
+ {
42
+ "type": "action",
43
+ "call": "SendTaskStartNotification",
44
+ "comment": "Broadcast to other agents that QA is starting review of this task"
45
+ },
46
+ {
47
+ "type": "action",
48
+ "call": "NoOp",
49
+ "comment": "Continue without notification if messaging is unavailable"
50
+ }
51
+ ]
52
+ },
53
+ {
54
+ "type": "selector",
55
+ "comment": "Main QA flow with failure handling",
56
+ "children": [
57
+ {
58
+ "type": "sequence",
59
+ "comment": "Main QA testing sequence",
60
+ "children": [
61
+ {
62
+ "type": "action",
63
+ "call": "ExtractTaskFiles",
64
+ "comment": "Extract task-specific file list from engineer's completion comment"
65
+ },
66
+ {
67
+ "type": "action",
68
+ "call": "GitDiff",
69
+ "comment": "Get git diff scoped to task-specific files when available"
70
+ },
71
+ {
72
+ "type": "selector",
73
+ "comment": "Check if there are changes to test",
74
+ "children": [
75
+ {
76
+ "type": "sequence",
77
+ "comment": "No changes detected - pass task without PR",
78
+ "children": [
79
+ {
80
+ "type": "flip",
81
+ "child": {
82
+ "type": "condition",
83
+ "call": "HasCodeChanges"
84
+ }
85
+ },
86
+ {
87
+ "type": "action",
88
+ "call": "AddNoChangesComment"
89
+ },
90
+ {
91
+ "type": "action",
92
+ "call": "RequestStatusDone"
93
+ }
94
+ ]
95
+ },
96
+ {
97
+ "type": "sequence",
98
+ "comment": "Changes detected - run unit tests",
99
+ "children": [
100
+ {
101
+ "type": "action",
102
+ "call": "DetectProjectType"
103
+ },
104
+ {
105
+ "type": "llm-action",
106
+ "name": "AnalyzeChanges",
107
+ "prompt": "You are a QA agent analyzing changes. Review the task and git diff.\n\nTask: {{taskDescription}}\nGit Diff: {{gitDiff}}\nProject Info: {{projectInfo}}\n\nBriefly summarize what was changed.",
108
+ "contextKeys": ["taskDescription", "taskTitle", "gitDiff", "projectInfo"],
109
+ "outputSchema": {
110
+ "type": "object",
111
+ "properties": {
112
+ "changesSummary": { "type": "string" },
113
+ "affectedAreas": { "type": "array", "items": { "type": "string" } },
114
+ "riskLevel": { "type": "string", "enum": ["low", "medium", "high"] }
115
+ },
116
+ "required": ["changesSummary", "riskLevel"]
117
+ },
118
+ "outputKey": "changeAnalysis",
119
+ "temperature": 0.3
120
+ },
121
+ {
122
+ "type": "action",
123
+ "call": "HasTests"
124
+ },
125
+ {
126
+ "type": "selector",
127
+ "comment": "Branch based on whether tests exist",
128
+ "children": [
129
+ {
130
+ "type": "sequence",
131
+ "comment": "Tests exist - run them with environment retry logic",
132
+ "children": [
133
+ {
134
+ "type": "condition",
135
+ "call": "HasTestsConfigured"
136
+ },
137
+ {
138
+ "type": "llm-action",
139
+ "name": "DetermineTestCommand",
140
+ "prompt": "Determine the correct test command for this project.\n\nProject Info: {{projectInfo}}\n\nCRITICAL: Check the runtime/package manager FIRST before choosing a test command.\n\nRuntime detection priority (check in this order):\n1. If projectInfo.primaryType is 'bun' OR detectedFiles include 'bun.lockb', 'bun.lock', or 'bunfig.toml' OR packageManager is 'bun': this is a BUN project. Use 'bun test'. Set projectType to 'bun'.\n2. If detectedFiles include 'yarn.lock': use 'yarn test'. Set projectType to 'node-yarn'.\n3. If detectedFiles include 'pnpm-lock.yaml': use 'pnpm test'. Set projectType to 'node-pnpm'.\n4. If only 'package.json' is detected with no specific lock file: use 'npm test'. Set projectType to 'node'.\n\nOther project types:\n- Rust (Cargo.toml): cargo test\n- Python (pyproject.toml, setup.py): pytest\n- Go (go.mod): go test ./...\n\nDo NOT default to 'npm test' when Bun indicators are present. A project with bun.lock or bun.lockb is a Bun project, not a Node.js project.",
141
+ "contextKeys": ["projectInfo", "testFilesFound"],
142
+ "outputSchema": {
143
+ "type": "object",
144
+ "properties": {
145
+ "projectType": { "type": "string", "enum": ["bun", "node", "node-yarn", "node-pnpm", "rust", "python", "go", "java-maven", "java-gradle", "ruby", "php", "elixir", "other"] },
146
+ "testCommand": { "type": "string" },
147
+ "reasoning": { "type": "string" }
148
+ },
149
+ "required": ["projectType", "testCommand", "reasoning"]
150
+ },
151
+ "outputKey": "testCommandInfo",
152
+ "temperature": 0.2
153
+ },
154
+ {
155
+ "type": "action",
156
+ "call": "ResetEnvRetryCount",
157
+ "comment": "Reset retry counter before test execution"
158
+ },
159
+ {
160
+ "type": "retry",
161
+ "attempts": 3,
162
+ "comment": "Retry test execution up to 3 times if environment issues occur",
163
+ "child": {
164
+ "type": "sequence",
165
+ "comment": "Test execution with environment fix on failure",
166
+ "children": [
167
+ {
168
+ "type": "action",
169
+ "call": "InstallDependencies",
170
+ "comment": "Install dependencies before running tests"
171
+ },
172
+ {
173
+ "type": "action",
174
+ "call": "ExecuteTestCommand"
175
+ },
176
+ {
177
+ "type": "selector",
178
+ "comment": "Try LLM test analysis, fall back to exit-code-based analysis if prompt is too long",
179
+ "children": [
180
+ {
181
+ "type": "llm-action",
182
+ "name": "AnalyzeTestResults",
183
+ "prompt": "Analyze the test results in the context of what files were changed.\n\nTest Output: {{testResults}}\nTest Command: {{testCommandInfo}}\nGit Diff (files changed by engineer): {{gitDiff}}\nTask Files: {{taskFiles}}\nChange Analysis: {{changeAnalysis}}\n\nYour job is to determine if the engineer's changes CAUSED any test failures. You MUST distinguish between:\n\n1. **Task-related failures**: Tests that fail because of code the engineer changed or added. These are in files listed in the git diff or task files, or test files that directly import/test those changed modules. These are legitimate failures.\n\n2. **Pre-existing/unrelated failures**: Tests that fail in modules the engineer did NOT touch. These failures existed BEFORE the engineer's changes and are NOT the engineer's responsibility. Do NOT count these as failures.\n\n3. **Environment issues**: Test runner not found (exit code 127), dependencies not installed, 'command not found' errors, missing optional dependencies (@rollup/rollup-*, @esbuild/*), module resolution errors. These are QA environment issues, NOT code issues.\n\nIMPORTANT: If ONLY environment issues occurred (no tests actually ran), set 'passed' to false and classify failures as 'environment'.\n\nSet 'passed' to true ONLY if:\n- Tests actually executed AND\n- There are NO task-related failures\n\nSet 'passed' to false if:\n- Environment issues prevented tests from running, OR\n- There are task-related failures\n\nFor each failure, classify it as 'task-related', 'pre-existing', or 'environment' in the classification field.",
184
+ "contextKeys": ["testResults", "testCommandInfo", "changeAnalysis", "gitDiff", "taskFiles"],
185
+ "outputSchema": {
186
+ "type": "object",
187
+ "properties": {
188
+ "passed": { "type": "boolean", "description": "true ONLY if tests actually ran and no task-related failures exist. false if env issues prevented execution." },
189
+ "testsActuallyRan": { "type": "boolean", "description": "true if tests actually executed, false if blocked by environment issues" },
190
+ "totalTests": { "type": ["number", "null"] },
191
+ "passedTests": { "type": ["number", "null"] },
192
+ "failedTests": { "type": ["number", "null"] },
193
+ "taskRelatedFailures": { "type": ["number", "null"], "description": "Number of failures caused by the engineer's changes" },
194
+ "preExistingFailures": { "type": ["number", "null"], "description": "Number of failures that existed before the engineer's changes" },
195
+ "environmentIssues": { "type": ["number", "null"], "description": "Number of environment-related issues (missing deps, command not found, etc.)" },
196
+ "failures": {
197
+ "type": "array",
198
+ "items": {
199
+ "type": "object",
200
+ "properties": {
201
+ "testName": { "type": "string" },
202
+ "error": { "type": "string" },
203
+ "classification": { "type": "string", "enum": ["task-related", "pre-existing", "environment"], "description": "Whether this failure is caused by the engineer's changes, pre-existing, or an environment issue" }
204
+ }
205
+ }
206
+ },
207
+ "summary": { "type": "string" }
208
+ },
209
+ "required": ["passed", "summary"]
210
+ },
211
+ "outputKey": "analyzedTestResults",
212
+ "temperature": 0.2
213
+ },
214
+ {
215
+ "type": "action",
216
+ "call": "FallbackTestAnalysis",
217
+ "comment": "Fallback: use test exit code when LLM cannot analyze (prompt too long)"
218
+ }
219
+ ]
220
+ },
221
+ {
222
+ "type": "selector",
223
+ "comment": "Check for environment issues - if found, fix and fail to trigger retry",
224
+ "children": [
225
+ {
226
+ "type": "sequence",
227
+ "comment": "Environment issue detected - attempt fix and retry",
228
+ "children": [
229
+ {
230
+ "type": "condition",
231
+ "call": "HasEnvironmentIssue"
232
+ },
233
+ {
234
+ "type": "action",
235
+ "call": "IncrementEnvRetryCount"
236
+ },
237
+ {
238
+ "type": "selector",
239
+ "comment": "Check if we've hit max retries",
240
+ "children": [
241
+ {
242
+ "type": "sequence",
243
+ "comment": "Max retries reached - give up on env fixes, continue with analysis",
244
+ "children": [
245
+ {
246
+ "type": "condition",
247
+ "call": "HasMaxEnvRetries"
248
+ },
249
+ {
250
+ "type": "action",
251
+ "call": "NoOp",
252
+ "comment": "Allow flow to continue past retry to final analysis"
253
+ }
254
+ ]
255
+ },
256
+ {
257
+ "type": "sequence",
258
+ "comment": "Still have retries left - fix environment and fail to trigger retry",
259
+ "children": [
260
+ {
261
+ "type": "action",
262
+ "call": "FixEnvironmentIssue"
263
+ },
264
+ {
265
+ "type": "fail",
266
+ "comment": "Fail to trigger retry with fixed environment",
267
+ "child": {
268
+ "type": "action",
269
+ "call": "NoOp"
270
+ }
271
+ }
272
+ ]
273
+ }
274
+ ]
275
+ }
276
+ ]
277
+ },
278
+ {
279
+ "type": "action",
280
+ "call": "NoOp",
281
+ "comment": "No environment issue - continue to test result analysis"
282
+ }
283
+ ]
284
+ }
285
+ ]
286
+ }
287
+ },
288
+ {
289
+ "type": "selector",
290
+ "comment": "Handle pass or fail based on actual test results",
291
+ "children": [
292
+ {
293
+ "type": "sequence",
294
+ "comment": "Tests passed - approve",
295
+ "children": [
296
+ {
297
+ "type": "llm-condition",
298
+ "name": "TestsPassed",
299
+ "prompt": "Based on the analyzed test results, did the engineer's changes pass QA?\n\nTest Results: {{analyzedTestResults}}\nGit Diff: {{gitDiff}}\nTask Files: {{taskFiles}}\nEnvironment Retry Count: {{envRetryCount}}\n\nReturn true if the engineer's changes did NOT introduce any new test failures AND tests actually executed.\n\nCRITICAL RULES:\n1. If 'passed' is true in analyzedTestResults AND tests actually ran, return true.\n2. If 'taskRelatedFailures' is 0 or null AND tests ran, return true — even if there are pre-existing failures.\n3. Pre-existing failures (tests failing in code the engineer did NOT touch) do NOT count as the engineer's fault. Return true.\n4. ONLY return false if:\n - There are failures directly caused by the engineer's changes (classification: 'task-related'), OR\n - Tests did NOT actually execute (environment issues prevented them from running even after retries)\n5. If 'testsActuallyRan' is false or all failures are 'environment' type, return false - we need to see actual test results.\n6. Environment issues that could not be fixed after retries should result in false (tests didn't run).",
300
+ "contextKeys": ["analyzedTestResults", "gitDiff", "taskFiles", "envRetryCount"],
301
+ "confidenceThreshold": 0.8,
302
+ "fallbackValue": false
303
+ },
304
+ {
305
+ "type": "llm-action",
306
+ "name": "WriteApprovalComment",
307
+ "prompt": "Write a brief approval comment.\n\nTask: {{taskDescription}}\nTest Results: {{analyzedTestResults}}\n\nKeep it short. If there were pre-existing test failures (not caused by the engineer), mention them briefly as known pre-existing issues that are not blocking.",
308
+ "contextKeys": ["taskDescription", "analyzedTestResults"],
309
+ "outputSchema": {
310
+ "type": "object",
311
+ "properties": {
312
+ "comment": { "type": "string" }
313
+ },
314
+ "required": ["comment"]
315
+ },
316
+ "outputKey": "approvalComment",
317
+ "temperature": 0.4
318
+ },
319
+ {
320
+ "type": "action",
321
+ "call": "AddApprovalComment"
322
+ },
323
+ {
324
+ "type": "action",
325
+ "call": "RunLinter",
326
+ "comment": "Run linter before push - succeeds silently if no linter found"
327
+ },
328
+ {
329
+ "type": "selector",
330
+ "comment": "Check lint results - pass to push or fail back to engineer",
331
+ "children": [
332
+ {
333
+ "type": "sequence",
334
+ "comment": "Lint passed (or no linter) - proceed to push",
335
+ "children": [
336
+ {
337
+ "type": "condition",
338
+ "call": "LintPassed"
339
+ },
340
+ {
341
+ "type": "action",
342
+ "call": "GitPush"
343
+ },
344
+ {
345
+ "type": "action",
346
+ "call": "GitCreatePR"
347
+ },
348
+ {
349
+ "type": "action",
350
+ "call": "RequestStatusDone"
351
+ }
352
+ ]
353
+ },
354
+ {
355
+ "type": "sequence",
356
+ "comment": "Lint failed - send back to engineer with lint errors",
357
+ "children": [
358
+ {
359
+ "type": "condition",
360
+ "call": "LintFailed"
361
+ },
362
+ {
363
+ "type": "action",
364
+ "call": "AddLintFailureComment"
365
+ },
366
+ {
367
+ "type": "action",
368
+ "call": "RequestStatusTodo"
369
+ }
370
+ ]
371
+ }
372
+ ]
373
+ }
374
+ ]
375
+ },
376
+ {
377
+ "type": "sequence",
378
+ "comment": "Tests failed or did not execute - handle appropriately",
379
+ "children": [
380
+ {
381
+ "type": "selector",
382
+ "comment": "Check if this is an unresolved environment issue or actual test failure",
383
+ "children": [
384
+ {
385
+ "type": "sequence",
386
+ "comment": "Environment issue persists after max retries - block task for human review",
387
+ "children": [
388
+ {
389
+ "type": "condition",
390
+ "call": "HasEnvironmentIssue"
391
+ },
392
+ {
393
+ "type": "condition",
394
+ "call": "HasMaxEnvRetries"
395
+ },
396
+ {
397
+ "type": "llm-action",
398
+ "name": "DocumentEnvironmentBlocker",
399
+ "prompt": "Document that the task is blocked due to unresolved environment issues.\n\nTask: {{taskDescription}}\nTest Results: {{analyzedTestResults}}\nRaw Test Output: {{testResults}}\nFix Attempts: {{environmentFixResults}}\nRetry Count: {{envRetryCount}}\n\nExplain that:\n1. The QA agent attempted to run tests but encountered environment issues\n2. Multiple attempts to fix the environment (installing dependencies, etc.) failed\n3. This is NOT a code issue with the engineer's changes\n4. Human intervention is needed to resolve the test environment\n\nBe specific about what environment issue occurred (missing dependency, command not found, etc.).",
400
+ "contextKeys": ["taskDescription", "analyzedTestResults", "testResults", "environmentFixResults", "envRetryCount"],
401
+ "outputSchema": {
402
+ "type": "object",
403
+ "properties": {
404
+ "rejectionReason": { "type": "string" },
405
+ "environmentIssue": { "type": "string" },
406
+ "fixAttempts": { "type": "array", "items": { "type": "string" } },
407
+ "comment": { "type": "string" }
408
+ },
409
+ "required": ["rejectionReason", "environmentIssue", "comment"]
410
+ },
411
+ "outputKey": "rejectionDetails",
412
+ "temperature": 0.3
413
+ },
414
+ {
415
+ "type": "action",
416
+ "call": "AddEnvironmentBlockerComment"
417
+ },
418
+ {
419
+ "type": "action",
420
+ "call": "RequestStatusBlocked"
421
+ }
422
+ ]
423
+ },
424
+ {
425
+ "type": "sequence",
426
+ "comment": "Actual test failures - reject and send back to engineer",
427
+ "children": [
428
+ {
429
+ "type": "llm-action",
430
+ "name": "DocumentRejection",
431
+ "prompt": "Document why the task is rejected based ONLY on task-related test failures.\n\nTask: {{taskDescription}}\nTest Results: {{analyzedTestResults}}\nGit Diff: {{gitDiff}}\nTask Files: {{taskFiles}}\n\nExplain what failed and what needs fixing. ONLY include failures that are classified as 'task-related' — failures in code the engineer actually changed.\n\nCRITICAL RULES:\n1. NEVER reject for pre-existing failures (tests failing in code the engineer did NOT touch).\n2. NEVER reject because dependencies were not installed, test runners were not found (exit code 127), or the test environment was not set up.\n3. ONLY reject for actual code failures in the engineer's changed files: tests that fail due to bugs, missing implementations, incorrect logic, or code that does not meet acceptance criteria.\n4. If the only failures are pre-existing or environment-related, this rejection should NOT have been reached — but if it was, explain that the failures are not task-related and recommend approval.",
432
+ "contextKeys": ["taskDescription", "analyzedTestResults", "testResults", "gitDiff", "taskFiles"],
433
+ "outputSchema": {
434
+ "type": "object",
435
+ "properties": {
436
+ "rejectionReason": { "type": "string" },
437
+ "requiredFixes": {
438
+ "type": "array",
439
+ "items": {
440
+ "type": "object",
441
+ "properties": {
442
+ "issue": { "type": "string" },
443
+ "suggestedFix": { "type": "string" },
444
+ "priority": { "type": "string", "enum": ["high", "medium", "low"] }
445
+ }
446
+ }
447
+ },
448
+ "comment": { "type": "string" }
449
+ },
450
+ "required": ["rejectionReason", "comment"]
451
+ },
452
+ "outputKey": "rejectionDetails",
453
+ "temperature": 0.3
454
+ },
455
+ {
456
+ "type": "action",
457
+ "call": "AddRejectionComment"
458
+ },
459
+ {
460
+ "type": "action",
461
+ "call": "RequestStatusTodo"
462
+ }
463
+ ]
464
+ }
465
+ ]
466
+ }
467
+ ]
468
+ }
469
+ ]
470
+ }
471
+ ]
472
+ },
473
+ {
474
+ "type": "sequence",
475
+ "comment": "No tests - approve with note if low risk",
476
+ "children": [
477
+ {
478
+ "type": "llm-condition",
479
+ "name": "IsLowRisk",
480
+ "prompt": "Is this a low-risk change that can be approved without tests?\n\nChange Analysis: {{changeAnalysis}}\nTask: {{taskDescription}}\n\nLow risk = docs, config, minor refactoring.\nHigh risk = core logic, security, data.\n\nReturn true if low risk.",
481
+ "contextKeys": ["changeAnalysis", "taskDescription"],
482
+ "confidenceThreshold": 0.7,
483
+ "fallbackValue": false
484
+ },
485
+ {
486
+ "type": "action",
487
+ "call": "AddNoTestsComment"
488
+ },
489
+ {
490
+ "type": "action",
491
+ "call": "RunLinter",
492
+ "comment": "Run linter before push - succeeds silently if no linter found"
493
+ },
494
+ {
495
+ "type": "selector",
496
+ "comment": "Check lint results - pass to push or fail back to engineer",
497
+ "children": [
498
+ {
499
+ "type": "sequence",
500
+ "comment": "Lint passed (or no linter) - proceed to push",
501
+ "children": [
502
+ {
503
+ "type": "condition",
504
+ "call": "LintPassed"
505
+ },
506
+ {
507
+ "type": "action",
508
+ "call": "GitPush"
509
+ },
510
+ {
511
+ "type": "action",
512
+ "call": "GitCreatePR"
513
+ },
514
+ {
515
+ "type": "action",
516
+ "call": "RequestStatusDone"
517
+ }
518
+ ]
519
+ },
520
+ {
521
+ "type": "sequence",
522
+ "comment": "Lint failed - send back to engineer with lint errors",
523
+ "children": [
524
+ {
525
+ "type": "condition",
526
+ "call": "LintFailed"
527
+ },
528
+ {
529
+ "type": "action",
530
+ "call": "AddLintFailureComment"
531
+ },
532
+ {
533
+ "type": "action",
534
+ "call": "RequestStatusTodo"
535
+ }
536
+ ]
537
+ }
538
+ ]
539
+ }
540
+ ]
541
+ },
542
+ {
543
+ "type": "sequence",
544
+ "comment": "No tests and high risk - reject",
545
+ "children": [
546
+ {
547
+ "type": "llm-action",
548
+ "name": "DocumentTestsRequired",
549
+ "prompt": "Explain why tests are needed.\n\nTask: {{taskDescription}}\nChange Analysis: {{changeAnalysis}}\n\nThis is a high-risk change that needs tests.",
550
+ "contextKeys": ["taskDescription", "changeAnalysis"],
551
+ "outputSchema": {
552
+ "type": "object",
553
+ "properties": {
554
+ "rejectionReason": { "type": "string" },
555
+ "requiredFixes": {
556
+ "type": "array",
557
+ "items": {
558
+ "type": "object",
559
+ "properties": {
560
+ "issue": { "type": "string" },
561
+ "suggestedFix": { "type": "string" },
562
+ "priority": { "type": "string", "enum": ["high", "medium", "low"] }
563
+ }
564
+ }
565
+ },
566
+ "comment": { "type": "string" }
567
+ },
568
+ "required": ["rejectionReason", "comment"]
569
+ },
570
+ "outputKey": "rejectionDetails",
571
+ "temperature": 0.3
572
+ },
573
+ {
574
+ "type": "action",
575
+ "call": "AddRejectionComment"
576
+ },
577
+ {
578
+ "type": "action",
579
+ "call": "RequestStatusTodo"
580
+ }
581
+ ]
582
+ }
583
+ ]
584
+ }
585
+ ]
586
+ }
587
+ ]
588
+ }
589
+ ]
590
+ },
591
+ {
592
+ "type": "sequence",
593
+ "comment": "FAILURE HANDLER: Send back to engineer via bounce mechanism (blocks after maxQABounces)",
594
+ "children": [
595
+ {
596
+ "type": "action",
597
+ "call": "AddQAFailureComment"
598
+ },
599
+ {
600
+ "type": "action",
601
+ "call": "RequestStatusTodo"
602
+ }
603
+ ]
604
+ }
605
+ ]
606
+ },
607
+ {
608
+ "type": "selector",
609
+ "comment": "Notify other agents that QA review is complete (non-critical: continue even if unavailable)",
610
+ "children": [
611
+ {
612
+ "type": "action",
613
+ "call": "SendTaskCompleteNotification",
614
+ "comment": "Broadcast to other agents that QA has finished reviewing this task"
615
+ },
616
+ {
617
+ "type": "action",
618
+ "call": "NoOp",
619
+ "comment": "Continue without notification if messaging is unavailable"
620
+ }
621
+ ]
622
+ },
623
+ {
624
+ "type": "action",
625
+ "call": "ClearTaskContext",
626
+ "comment": "Reset task-specific blackboard keys to prepare for next task assignment"
627
+ },
628
+ {
629
+ "type": "action",
630
+ "call": "Loop",
631
+ "comment": "Return RUNNING to restart the BT from the root - wait for next task"
632
+ }
633
+ ]
634
+ }
635
+ },
636
+ "blackboardDefaults": {
637
+ "changeAnalysis": null,
638
+ "testResults": null,
639
+ "analyzedTestResults": null,
640
+ "approvalComment": null,
641
+ "rejectionDetails": null,
642
+ "gitDiff": null,
643
+ "hasTests": null,
644
+ "testFilesFound": null,
645
+ "hasValidTestScript": null,
646
+ "testScriptValue": null,
647
+ "maxRetries": 2,
648
+ "maxEnvRetries": 2,
649
+ "envRetryCount": 0,
650
+ "environmentFixAttempted": false,
651
+ "environmentFixResults": null,
652
+ "projectInfo": null,
653
+ "testCommandInfo": null,
654
+ "testExitCode": null,
655
+ "requestedStatus": null,
656
+ "statusChangeReason": null,
657
+ "taskComments": null,
658
+ "taskDetails": null,
659
+ "qaBounceCount": 0,
660
+ "maxQABounces": 3,
661
+ "taskFiles": null,
662
+ "lintResults": null,
663
+ "lintCommand": null,
664
+ "lintDetected": null,
665
+ "lintPassed": null,
666
+ "lintErrors": null,
667
+ "lintSource": null,
668
+ "currentTaskId": null,
669
+ "taskAssignedAt": null,
670
+ "loopCount": 0,
671
+ "isQARejection": false,
672
+ "hasQAFeedback": false,
673
+ "incomingMessages": [],
674
+ "coordinationMessage": null,
675
+ "messageResponse": null
676
+ }
677
+ }