adi_dev_workflow 1.1.1 → 1.3.0

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 (98) hide show
  1. package/bin/index.js +8 -8
  2. package/frameworks/agents/qa-staff-engineer.md +311 -311
  3. package/frameworks/agents/qa-validation-expert.md +458 -458
  4. package/frameworks/agents/tech-review-conformance.md +200 -200
  5. package/frameworks/commands/ministack/README.md +2 -0
  6. package/frameworks/commands/ministack/code-review.md +2 -0
  7. package/frameworks/commands/ministack/generate-intent.md +2 -0
  8. package/frameworks/commands/ministack/generate-scope.md +2 -0
  9. package/frameworks/commands/ministack/generate-tasks.md +2 -0
  10. package/frameworks/commands/ministack/generate-tech-direction.md +2 -0
  11. package/frameworks/commands/ministack/run-ministack-tasks.md +3 -0
  12. package/frameworks/commands/ministack/run-ministack-withlinear.md +2 -0
  13. package/frameworks/commands/ministack/status.md +2 -0
  14. package/frameworks/commands/sdd/code-review.md +2 -0
  15. package/frameworks/commands/sdd/generate-prd.md +2 -0
  16. package/frameworks/commands/sdd/generate-task-plan.md +2 -0
  17. package/frameworks/commands/sdd/generate-tech-direction.md +2 -0
  18. package/frameworks/commands/sdd/generate-tech-spec.md +2 -0
  19. package/frameworks/commands/sdd/generate-tests.md +2 -0
  20. package/frameworks/commands/sdd/run_tasks.md +3 -0
  21. package/frameworks/commands/sdd/run_tasks_withlinear.md +2 -0
  22. package/frameworks/commands/sdd/status.md +2 -0
  23. package/frameworks/commands/sdd/validate-sdd.md +2 -0
  24. package/frameworks/commands/sync-tasks-to-linear.md +2 -0
  25. package/frameworks/commands/taskcard/generate-taskcard.md +2 -0
  26. package/frameworks/commands/taskcard/run-taskcard.md +2 -0
  27. package/frameworks/config/ai-framework-config.yaml +112 -0
  28. package/frameworks/skills/ministack-tasks-expert/SKILL.md +204 -204
  29. package/frameworks/skills/ministack-tasks-expert/templates/task_plan_template.md +78 -78
  30. package/frameworks/skills/ministack-tasks-expert/templates/task_template.md +103 -103
  31. package/frameworks/skills/sdd-task-plan-expert-workspace/iteration-1/benchmark.json +99 -99
  32. package/frameworks/skills/sdd-task-plan-expert-workspace/iteration-1/benchmark.md +64 -64
  33. package/frameworks/skills/sdd-task-plan-expert-workspace/iteration-1/eval-1-happy-path/eval_metadata.json +12 -12
  34. package/frameworks/skills/sdd-task-plan-expert-workspace/iteration-1/eval-1-happy-path/with_skill/grading.json +32 -32
  35. package/frameworks/skills/sdd-task-plan-expert-workspace/iteration-1/eval-1-happy-path/with_skill/outputs/response.md +134 -134
  36. package/frameworks/skills/sdd-task-plan-expert-workspace/iteration-1/eval-1-happy-path/with_skill/outputs/transcript.md +68 -68
  37. package/frameworks/skills/sdd-task-plan-expert-workspace/iteration-1/eval-1-happy-path/with_skill/timing.json +5 -5
  38. package/frameworks/skills/sdd-task-plan-expert-workspace/iteration-1/eval-1-happy-path/without_skill/grading.json +32 -32
  39. package/frameworks/skills/sdd-task-plan-expert-workspace/iteration-1/eval-1-happy-path/without_skill/outputs/response.md +525 -525
  40. package/frameworks/skills/sdd-task-plan-expert-workspace/iteration-1/eval-1-happy-path/without_skill/outputs/transcript.md +30 -30
  41. package/frameworks/skills/sdd-task-plan-expert-workspace/iteration-1/eval-1-happy-path/without_skill/timing.json +5 -5
  42. package/frameworks/skills/sdd-task-plan-expert-workspace/iteration-1/eval-2-spec-simples/eval_metadata.json +12 -12
  43. package/frameworks/skills/sdd-task-plan-expert-workspace/iteration-1/eval-2-spec-simples/with_skill/grading.json +32 -32
  44. package/frameworks/skills/sdd-task-plan-expert-workspace/iteration-1/eval-2-spec-simples/with_skill/outputs/response.md +1126 -1126
  45. package/frameworks/skills/sdd-task-plan-expert-workspace/iteration-1/eval-2-spec-simples/with_skill/outputs/transcript.md +131 -131
  46. package/frameworks/skills/sdd-task-plan-expert-workspace/iteration-1/eval-2-spec-simples/with_skill/timing.json +5 -5
  47. package/frameworks/skills/sdd-task-plan-expert-workspace/iteration-1/eval-2-spec-simples/without_skill/grading.json +32 -32
  48. package/frameworks/skills/sdd-task-plan-expert-workspace/iteration-1/eval-2-spec-simples/without_skill/outputs/response.md +452 -452
  49. package/frameworks/skills/sdd-task-plan-expert-workspace/iteration-1/eval-2-spec-simples/without_skill/outputs/transcript.md +78 -78
  50. package/frameworks/skills/sdd-task-plan-expert-workspace/iteration-1/eval-2-spec-simples/without_skill/timing.json +5 -5
  51. package/frameworks/skills/sdd-task-plan-expert-workspace/iteration-1/eval-3-sem-user-stories/eval_metadata.json +12 -12
  52. package/frameworks/skills/sdd-task-plan-expert-workspace/iteration-1/eval-3-sem-user-stories/with_skill/grading.json +32 -32
  53. package/frameworks/skills/sdd-task-plan-expert-workspace/iteration-1/eval-3-sem-user-stories/with_skill/outputs/response.md +101 -101
  54. package/frameworks/skills/sdd-task-plan-expert-workspace/iteration-1/eval-3-sem-user-stories/with_skill/outputs/transcript.md +133 -133
  55. package/frameworks/skills/sdd-task-plan-expert-workspace/iteration-1/eval-3-sem-user-stories/with_skill/timing.json +5 -5
  56. package/frameworks/skills/sdd-task-plan-expert-workspace/iteration-1/eval-3-sem-user-stories/without_skill/grading.json +32 -32
  57. package/frameworks/skills/sdd-task-plan-expert-workspace/iteration-1/eval-3-sem-user-stories/without_skill/outputs/response.md +248 -248
  58. package/frameworks/skills/sdd-task-plan-expert-workspace/iteration-1/eval-3-sem-user-stories/without_skill/outputs/transcript.md +49 -49
  59. package/frameworks/skills/sdd-task-plan-expert-workspace/iteration-1/eval-3-sem-user-stories/without_skill/timing.json +5 -5
  60. package/frameworks/skills/sdd-task-plan-expert-workspace/iteration-1/review.html +1325 -1325
  61. package/frameworks/skills/sdd-task-plan-expert-workspace/iteration-2/benchmark.json +94 -94
  62. package/frameworks/skills/sdd-task-plan-expert-workspace/iteration-2/benchmark.md +67 -67
  63. package/frameworks/skills/sdd-task-plan-expert-workspace/iteration-2/eval-1-happy-path/eval_metadata.json +12 -12
  64. package/frameworks/skills/sdd-task-plan-expert-workspace/iteration-2/eval-1-happy-path/with_skill/grading.json +32 -32
  65. package/frameworks/skills/sdd-task-plan-expert-workspace/iteration-2/eval-1-happy-path/with_skill/outputs/response.md +117 -117
  66. package/frameworks/skills/sdd-task-plan-expert-workspace/iteration-2/eval-1-happy-path/with_skill/outputs/transcript.md +91 -91
  67. package/frameworks/skills/sdd-task-plan-expert-workspace/iteration-2/eval-1-happy-path/with_skill/timing.json +1 -1
  68. package/frameworks/skills/sdd-task-plan-expert-workspace/iteration-2/eval-1-happy-path/without_skill/grading.json +32 -32
  69. package/frameworks/skills/sdd-task-plan-expert-workspace/iteration-2/eval-1-happy-path/without_skill/outputs/response.md +694 -694
  70. package/frameworks/skills/sdd-task-plan-expert-workspace/iteration-2/eval-1-happy-path/without_skill/outputs/transcript.md +45 -45
  71. package/frameworks/skills/sdd-task-plan-expert-workspace/iteration-2/eval-1-happy-path/without_skill/timing.json +1 -1
  72. package/frameworks/skills/sdd-task-plan-expert-workspace/iteration-2/eval-2-spec-simples/eval_metadata.json +12 -12
  73. package/frameworks/skills/sdd-task-plan-expert-workspace/iteration-2/eval-2-spec-simples/with_skill/grading.json +32 -32
  74. package/frameworks/skills/sdd-task-plan-expert-workspace/iteration-2/eval-2-spec-simples/with_skill/outputs/response.md +1087 -1087
  75. package/frameworks/skills/sdd-task-plan-expert-workspace/iteration-2/eval-2-spec-simples/with_skill/outputs/transcript.md +124 -124
  76. package/frameworks/skills/sdd-task-plan-expert-workspace/iteration-2/eval-2-spec-simples/with_skill/timing.json +1 -1
  77. package/frameworks/skills/sdd-task-plan-expert-workspace/iteration-2/eval-2-spec-simples/without_skill/grading.json +32 -32
  78. package/frameworks/skills/sdd-task-plan-expert-workspace/iteration-2/eval-2-spec-simples/without_skill/outputs/response.md +458 -458
  79. package/frameworks/skills/sdd-task-plan-expert-workspace/iteration-2/eval-2-spec-simples/without_skill/outputs/transcript.md +84 -84
  80. package/frameworks/skills/sdd-task-plan-expert-workspace/iteration-2/eval-2-spec-simples/without_skill/timing.json +1 -1
  81. package/frameworks/skills/sdd-task-plan-expert-workspace/iteration-2/eval-3-sem-user-stories/eval_metadata.json +12 -12
  82. package/frameworks/skills/sdd-task-plan-expert-workspace/iteration-2/eval-3-sem-user-stories/with_skill/grading.json +32 -32
  83. package/frameworks/skills/sdd-task-plan-expert-workspace/iteration-2/eval-3-sem-user-stories/with_skill/outputs/response.md +70 -70
  84. package/frameworks/skills/sdd-task-plan-expert-workspace/iteration-2/eval-3-sem-user-stories/with_skill/outputs/transcript.md +148 -148
  85. package/frameworks/skills/sdd-task-plan-expert-workspace/iteration-2/eval-3-sem-user-stories/with_skill/timing.json +1 -1
  86. package/frameworks/skills/sdd-task-plan-expert-workspace/iteration-2/eval-3-sem-user-stories/without_skill/grading.json +32 -32
  87. package/frameworks/skills/sdd-task-plan-expert-workspace/iteration-2/eval-3-sem-user-stories/without_skill/outputs/response.md +249 -249
  88. package/frameworks/skills/sdd-task-plan-expert-workspace/iteration-2/eval-3-sem-user-stories/without_skill/outputs/transcript.md +80 -80
  89. package/frameworks/skills/sdd-task-plan-expert-workspace/iteration-2/eval-3-sem-user-stories/without_skill/timing.json +1 -1
  90. package/frameworks/skills/sdd-task-plan-expert-workspace/iteration-2/review.html +1325 -1325
  91. package/frameworks/skills/sdd-tech-spec-expert/SKILL.md +317 -317
  92. package/frameworks/skills/sdd-tech-spec-expert/evals/evals.json +199 -199
  93. package/frameworks/skills/sdd-tech-spec-expert/templates/spec_tech_template.md +290 -290
  94. package/frameworks/skills/sdd-tech-spec-expert/templates/tech_direction-template.md +23 -23
  95. package/package.json +28 -28
  96. package/src/cli.js +121 -121
  97. package/src/installer.js +155 -136
  98. package/src/transformer.js +86 -86
@@ -1,1325 +1,1325 @@
1
- <!DOCTYPE html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="UTF-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>Eval Review</title>
7
- <link rel="preconnect" href="https://fonts.googleapis.com">
8
- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
9
- <link href="https://fonts.googleapis.com/css2?family=Poppins:wght@500;600&family=Lora:wght@400;500&display=swap" rel="stylesheet">
10
- <script src="https://cdn.sheetjs.com/xlsx-0.20.3/package/dist/xlsx.full.min.js" integrity="sha384-EnyY0/GSHQGSxSgMwaIPzSESbqoOLSexfnSMN2AP+39Ckmn92stwABZynq1JyzdT" crossorigin="anonymous"></script>
11
- <style>
12
- :root {
13
- --bg: #faf9f5;
14
- --surface: #ffffff;
15
- --border: #e8e6dc;
16
- --text: #141413;
17
- --text-muted: #b0aea5;
18
- --accent: #d97757;
19
- --accent-hover: #c4613f;
20
- --green: #788c5d;
21
- --green-bg: #eef2e8;
22
- --red: #c44;
23
- --red-bg: #fceaea;
24
- --header-bg: #141413;
25
- --header-text: #faf9f5;
26
- --radius: 6px;
27
- }
28
-
29
- * { box-sizing: border-box; margin: 0; padding: 0; }
30
-
31
- body {
32
- font-family: 'Lora', Georgia, serif;
33
- background: var(--bg);
34
- color: var(--text);
35
- height: 100vh;
36
- display: flex;
37
- flex-direction: column;
38
- }
39
-
40
- /* ---- Header ---- */
41
- .header {
42
- background: var(--header-bg);
43
- color: var(--header-text);
44
- padding: 1rem 2rem;
45
- display: flex;
46
- justify-content: space-between;
47
- align-items: center;
48
- flex-shrink: 0;
49
- }
50
- .header h1 {
51
- font-family: 'Poppins', sans-serif;
52
- font-size: 1.25rem;
53
- font-weight: 600;
54
- }
55
- .header .instructions {
56
- font-size: 0.8rem;
57
- opacity: 0.7;
58
- margin-top: 0.25rem;
59
- }
60
- .header .progress {
61
- font-size: 0.875rem;
62
- opacity: 0.8;
63
- text-align: right;
64
- }
65
-
66
- /* ---- Main content ---- */
67
- .main {
68
- flex: 1;
69
- overflow-y: auto;
70
- padding: 1.5rem 2rem;
71
- display: flex;
72
- flex-direction: column;
73
- gap: 1.25rem;
74
- }
75
-
76
- /* ---- Sections ---- */
77
- .section {
78
- background: var(--surface);
79
- border: 1px solid var(--border);
80
- border-radius: var(--radius);
81
- flex-shrink: 0;
82
- }
83
- .section-header {
84
- font-family: 'Poppins', sans-serif;
85
- padding: 0.75rem 1rem;
86
- font-size: 0.75rem;
87
- font-weight: 500;
88
- text-transform: uppercase;
89
- letter-spacing: 0.05em;
90
- color: var(--text-muted);
91
- border-bottom: 1px solid var(--border);
92
- background: var(--bg);
93
- }
94
- .section-body {
95
- padding: 1rem;
96
- }
97
-
98
- /* ---- Config badge ---- */
99
- .config-badge {
100
- display: inline-block;
101
- padding: 0.2rem 0.625rem;
102
- border-radius: 9999px;
103
- font-family: 'Poppins', sans-serif;
104
- font-size: 0.6875rem;
105
- font-weight: 600;
106
- text-transform: uppercase;
107
- letter-spacing: 0.03em;
108
- margin-left: 0.75rem;
109
- vertical-align: middle;
110
- }
111
- .config-badge.config-primary {
112
- background: rgba(33, 150, 243, 0.12);
113
- color: #1976d2;
114
- }
115
- .config-badge.config-baseline {
116
- background: rgba(255, 193, 7, 0.15);
117
- color: #f57f17;
118
- }
119
-
120
- /* ---- Prompt ---- */
121
- .prompt-text {
122
- white-space: pre-wrap;
123
- font-size: 0.9375rem;
124
- line-height: 1.6;
125
- }
126
-
127
- /* ---- Outputs ---- */
128
- .output-file {
129
- border: 1px solid var(--border);
130
- border-radius: var(--radius);
131
- overflow: hidden;
132
- }
133
- .output-file + .output-file {
134
- margin-top: 1rem;
135
- }
136
- .output-file-header {
137
- padding: 0.5rem 0.75rem;
138
- font-size: 0.8rem;
139
- font-weight: 600;
140
- color: var(--text-muted);
141
- background: var(--bg);
142
- border-bottom: 1px solid var(--border);
143
- font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace;
144
- display: flex;
145
- justify-content: space-between;
146
- align-items: center;
147
- }
148
- .output-file-header .dl-btn {
149
- font-size: 0.7rem;
150
- color: var(--accent);
151
- text-decoration: none;
152
- cursor: pointer;
153
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
154
- font-weight: 500;
155
- opacity: 0.8;
156
- }
157
- .output-file-header .dl-btn:hover {
158
- opacity: 1;
159
- text-decoration: underline;
160
- }
161
- .output-file-content {
162
- padding: 0.75rem;
163
- overflow-x: auto;
164
- }
165
- .output-file-content pre {
166
- font-size: 0.8125rem;
167
- line-height: 1.5;
168
- white-space: pre-wrap;
169
- word-break: break-word;
170
- font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace;
171
- }
172
- .output-file-content img {
173
- max-width: 100%;
174
- height: auto;
175
- border-radius: 4px;
176
- }
177
- .output-file-content iframe {
178
- width: 100%;
179
- height: 600px;
180
- border: none;
181
- }
182
- .output-file-content table {
183
- border-collapse: collapse;
184
- font-size: 0.8125rem;
185
- width: 100%;
186
- }
187
- .output-file-content table td,
188
- .output-file-content table th {
189
- border: 1px solid var(--border);
190
- padding: 0.375rem 0.5rem;
191
- text-align: left;
192
- }
193
- .output-file-content table th {
194
- background: var(--bg);
195
- font-weight: 600;
196
- }
197
- .output-file-content .download-link {
198
- display: inline-flex;
199
- align-items: center;
200
- gap: 0.5rem;
201
- padding: 0.5rem 1rem;
202
- background: var(--bg);
203
- border: 1px solid var(--border);
204
- border-radius: 4px;
205
- color: var(--accent);
206
- text-decoration: none;
207
- font-size: 0.875rem;
208
- cursor: pointer;
209
- }
210
- .output-file-content .download-link:hover {
211
- background: var(--border);
212
- }
213
- .empty-state {
214
- color: var(--text-muted);
215
- font-style: italic;
216
- padding: 2rem;
217
- text-align: center;
218
- }
219
-
220
- /* ---- Feedback ---- */
221
- .prev-feedback {
222
- background: var(--bg);
223
- border: 1px solid var(--border);
224
- border-radius: 4px;
225
- padding: 0.625rem 0.75rem;
226
- margin-top: 0.75rem;
227
- font-size: 0.8125rem;
228
- color: var(--text-muted);
229
- line-height: 1.5;
230
- }
231
- .prev-feedback-label {
232
- font-size: 0.7rem;
233
- font-weight: 600;
234
- text-transform: uppercase;
235
- letter-spacing: 0.04em;
236
- margin-bottom: 0.25rem;
237
- color: var(--text-muted);
238
- }
239
- .feedback-textarea {
240
- width: 100%;
241
- min-height: 100px;
242
- padding: 0.75rem;
243
- border: 1px solid var(--border);
244
- border-radius: 4px;
245
- font-family: inherit;
246
- font-size: 0.9375rem;
247
- line-height: 1.5;
248
- resize: vertical;
249
- color: var(--text);
250
- }
251
- .feedback-textarea:focus {
252
- outline: none;
253
- border-color: var(--accent);
254
- box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.1);
255
- }
256
- .feedback-status {
257
- font-size: 0.75rem;
258
- color: var(--text-muted);
259
- margin-top: 0.5rem;
260
- min-height: 1.1em;
261
- }
262
-
263
- /* ---- Grades (collapsible) ---- */
264
- .grades-toggle {
265
- display: flex;
266
- align-items: center;
267
- cursor: pointer;
268
- user-select: none;
269
- }
270
- .grades-toggle:hover {
271
- color: var(--accent);
272
- }
273
- .grades-toggle .arrow {
274
- margin-right: 0.5rem;
275
- transition: transform 0.15s;
276
- font-size: 0.75rem;
277
- }
278
- .grades-toggle .arrow.open {
279
- transform: rotate(90deg);
280
- }
281
- .grades-content {
282
- display: none;
283
- margin-top: 0.75rem;
284
- }
285
- .grades-content.open {
286
- display: block;
287
- }
288
- .grades-summary {
289
- font-size: 0.875rem;
290
- margin-bottom: 0.75rem;
291
- display: flex;
292
- align-items: center;
293
- gap: 0.5rem;
294
- }
295
- .grade-badge {
296
- display: inline-block;
297
- padding: 0.125rem 0.5rem;
298
- border-radius: 9999px;
299
- font-size: 0.75rem;
300
- font-weight: 600;
301
- }
302
- .grade-pass { background: var(--green-bg); color: var(--green); }
303
- .grade-fail { background: var(--red-bg); color: var(--red); }
304
- .assertion-list {
305
- list-style: none;
306
- }
307
- .assertion-item {
308
- padding: 0.625rem 0;
309
- border-bottom: 1px solid var(--border);
310
- font-size: 0.8125rem;
311
- }
312
- .assertion-item:last-child { border-bottom: none; }
313
- .assertion-status {
314
- font-weight: 600;
315
- margin-right: 0.5rem;
316
- }
317
- .assertion-status.pass { color: var(--green); }
318
- .assertion-status.fail { color: var(--red); }
319
- .assertion-evidence {
320
- color: var(--text-muted);
321
- font-size: 0.75rem;
322
- margin-top: 0.25rem;
323
- padding-left: 1.5rem;
324
- }
325
-
326
- /* ---- View tabs ---- */
327
- .view-tabs {
328
- display: flex;
329
- gap: 0;
330
- padding: 0 2rem;
331
- background: var(--bg);
332
- border-bottom: 1px solid var(--border);
333
- flex-shrink: 0;
334
- }
335
- .view-tab {
336
- font-family: 'Poppins', sans-serif;
337
- padding: 0.625rem 1.25rem;
338
- font-size: 0.8125rem;
339
- font-weight: 500;
340
- cursor: pointer;
341
- border: none;
342
- background: none;
343
- color: var(--text-muted);
344
- border-bottom: 2px solid transparent;
345
- transition: all 0.15s;
346
- }
347
- .view-tab:hover { color: var(--text); }
348
- .view-tab.active {
349
- color: var(--accent);
350
- border-bottom-color: var(--accent);
351
- }
352
- .view-panel { display: none; }
353
- .view-panel.active { display: flex; flex-direction: column; flex: 1; overflow: hidden; }
354
-
355
- /* ---- Benchmark view ---- */
356
- .benchmark-view {
357
- padding: 1.5rem 2rem;
358
- overflow-y: auto;
359
- flex: 1;
360
- }
361
- .benchmark-table {
362
- border-collapse: collapse;
363
- background: var(--surface);
364
- border: 1px solid var(--border);
365
- border-radius: var(--radius);
366
- font-size: 0.8125rem;
367
- width: 100%;
368
- margin-bottom: 1.5rem;
369
- }
370
- .benchmark-table th, .benchmark-table td {
371
- padding: 0.625rem 0.75rem;
372
- text-align: left;
373
- border: 1px solid var(--border);
374
- }
375
- .benchmark-table th {
376
- font-family: 'Poppins', sans-serif;
377
- background: var(--header-bg);
378
- color: var(--header-text);
379
- font-weight: 500;
380
- font-size: 0.75rem;
381
- text-transform: uppercase;
382
- letter-spacing: 0.04em;
383
- }
384
- .benchmark-table tr:hover { background: var(--bg); }
385
- .benchmark-table tr.benchmark-row-with { background: rgba(33, 150, 243, 0.06); }
386
- .benchmark-table tr.benchmark-row-without { background: rgba(255, 193, 7, 0.06); }
387
- .benchmark-table tr.benchmark-row-with:hover { background: rgba(33, 150, 243, 0.12); }
388
- .benchmark-table tr.benchmark-row-without:hover { background: rgba(255, 193, 7, 0.12); }
389
- .benchmark-table tr.benchmark-row-avg { font-weight: 600; border-top: 2px solid var(--border); }
390
- .benchmark-table tr.benchmark-row-avg.benchmark-row-with { background: rgba(33, 150, 243, 0.12); }
391
- .benchmark-table tr.benchmark-row-avg.benchmark-row-without { background: rgba(255, 193, 7, 0.12); }
392
- .benchmark-delta-positive { color: var(--green); font-weight: 600; }
393
- .benchmark-delta-negative { color: var(--red); font-weight: 600; }
394
- .benchmark-notes {
395
- background: var(--surface);
396
- border: 1px solid var(--border);
397
- border-radius: var(--radius);
398
- padding: 1rem;
399
- }
400
- .benchmark-notes h3 {
401
- font-family: 'Poppins', sans-serif;
402
- font-size: 0.875rem;
403
- margin-bottom: 0.75rem;
404
- }
405
- .benchmark-notes ul {
406
- list-style: disc;
407
- padding-left: 1.25rem;
408
- }
409
- .benchmark-notes li {
410
- font-size: 0.8125rem;
411
- line-height: 1.6;
412
- margin-bottom: 0.375rem;
413
- }
414
- .benchmark-empty {
415
- color: var(--text-muted);
416
- font-style: italic;
417
- text-align: center;
418
- padding: 3rem;
419
- }
420
-
421
- /* ---- Navigation ---- */
422
- .nav {
423
- display: flex;
424
- justify-content: space-between;
425
- align-items: center;
426
- padding: 1rem 2rem;
427
- border-top: 1px solid var(--border);
428
- background: var(--surface);
429
- flex-shrink: 0;
430
- }
431
- .nav-btn {
432
- font-family: 'Poppins', sans-serif;
433
- padding: 0.5rem 1.25rem;
434
- border: 1px solid var(--border);
435
- border-radius: var(--radius);
436
- background: var(--surface);
437
- cursor: pointer;
438
- font-size: 0.875rem;
439
- font-weight: 500;
440
- color: var(--text);
441
- transition: all 0.15s;
442
- }
443
- .nav-btn:hover:not(:disabled) {
444
- background: var(--bg);
445
- border-color: var(--text-muted);
446
- }
447
- .nav-btn:disabled {
448
- opacity: 0.4;
449
- cursor: not-allowed;
450
- }
451
- .done-btn {
452
- font-family: 'Poppins', sans-serif;
453
- padding: 0.5rem 1.5rem;
454
- border: 1px solid var(--border);
455
- border-radius: var(--radius);
456
- background: var(--surface);
457
- color: var(--text);
458
- cursor: pointer;
459
- font-size: 0.875rem;
460
- font-weight: 500;
461
- transition: all 0.15s;
462
- }
463
- .done-btn:hover {
464
- background: var(--bg);
465
- border-color: var(--text-muted);
466
- }
467
- .done-btn.ready {
468
- border: none;
469
- background: var(--accent);
470
- color: white;
471
- font-weight: 600;
472
- }
473
- .done-btn.ready:hover {
474
- background: var(--accent-hover);
475
- }
476
- /* ---- Done overlay ---- */
477
- .done-overlay {
478
- display: none;
479
- position: fixed;
480
- inset: 0;
481
- background: rgba(0, 0, 0, 0.5);
482
- z-index: 100;
483
- justify-content: center;
484
- align-items: center;
485
- }
486
- .done-overlay.visible {
487
- display: flex;
488
- }
489
- .done-card {
490
- background: var(--surface);
491
- border-radius: 12px;
492
- padding: 2rem 3rem;
493
- text-align: center;
494
- box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
495
- max-width: 500px;
496
- }
497
- .done-card h2 {
498
- font-size: 1.5rem;
499
- margin-bottom: 0.5rem;
500
- }
501
- .done-card p {
502
- color: var(--text-muted);
503
- margin-bottom: 1.5rem;
504
- line-height: 1.5;
505
- }
506
- .done-card .btn-row {
507
- display: flex;
508
- gap: 0.5rem;
509
- justify-content: center;
510
- }
511
- .done-card button {
512
- padding: 0.5rem 1.25rem;
513
- border: 1px solid var(--border);
514
- border-radius: var(--radius);
515
- background: var(--surface);
516
- cursor: pointer;
517
- font-size: 0.875rem;
518
- }
519
- .done-card button:hover {
520
- background: var(--bg);
521
- }
522
- /* ---- Toast ---- */
523
- .toast {
524
- position: fixed;
525
- bottom: 5rem;
526
- left: 50%;
527
- transform: translateX(-50%);
528
- background: var(--header-bg);
529
- color: var(--header-text);
530
- padding: 0.625rem 1.25rem;
531
- border-radius: var(--radius);
532
- font-size: 0.875rem;
533
- opacity: 0;
534
- transition: opacity 0.3s;
535
- pointer-events: none;
536
- z-index: 200;
537
- }
538
- .toast.visible {
539
- opacity: 1;
540
- }
541
- </style>
542
- </head>
543
- <body>
544
- <div id="app" style="height:100vh; display:flex; flex-direction:column;">
545
- <div class="header">
546
- <div>
547
- <h1>Eval Review: <span id="skill-name"></span></h1>
548
- <div class="instructions">Review each output and leave feedback below. Navigate with arrow keys or buttons. When done, copy feedback and paste into Claude Code.</div>
549
- </div>
550
- <div class="progress" id="progress"></div>
551
- </div>
552
-
553
- <!-- View tabs (only shown when benchmark data exists) -->
554
- <div class="view-tabs" id="view-tabs" style="display:none;">
555
- <button class="view-tab active" onclick="switchView('outputs')">Outputs</button>
556
- <button class="view-tab" onclick="switchView('benchmark')">Benchmark</button>
557
- </div>
558
-
559
- <!-- Outputs panel (qualitative review) -->
560
- <div class="view-panel active" id="panel-outputs">
561
- <div class="main">
562
- <!-- Prompt -->
563
- <div class="section">
564
- <div class="section-header">Prompt <span class="config-badge" id="config-badge" style="display:none;"></span></div>
565
- <div class="section-body">
566
- <div class="prompt-text" id="prompt-text"></div>
567
- </div>
568
- </div>
569
-
570
- <!-- Outputs -->
571
- <div class="section">
572
- <div class="section-header">Output</div>
573
- <div class="section-body" id="outputs-body">
574
- <div class="empty-state">No output files found</div>
575
- </div>
576
- </div>
577
-
578
- <!-- Previous Output (collapsible) -->
579
- <div class="section" id="prev-outputs-section" style="display:none;">
580
- <div class="section-header">
581
- <div class="grades-toggle" onclick="togglePrevOutputs()">
582
- <span class="arrow" id="prev-outputs-arrow">&#9654;</span>
583
- Previous Output
584
- </div>
585
- </div>
586
- <div class="grades-content" id="prev-outputs-content"></div>
587
- </div>
588
-
589
- <!-- Grades (collapsible) -->
590
- <div class="section" id="grades-section" style="display:none;">
591
- <div class="section-header">
592
- <div class="grades-toggle" onclick="toggleGrades()">
593
- <span class="arrow" id="grades-arrow">&#9654;</span>
594
- Formal Grades
595
- </div>
596
- </div>
597
- <div class="grades-content" id="grades-content"></div>
598
- </div>
599
-
600
- <!-- Feedback -->
601
- <div class="section">
602
- <div class="section-header">Your Feedback</div>
603
- <div class="section-body">
604
- <textarea
605
- class="feedback-textarea"
606
- id="feedback"
607
- placeholder="What do you think of this output? Any issues, suggestions, or things that look great?"
608
- ></textarea>
609
- <div class="feedback-status" id="feedback-status"></div>
610
- <div class="prev-feedback" id="prev-feedback" style="display:none;">
611
- <div class="prev-feedback-label">Previous feedback</div>
612
- <div id="prev-feedback-text"></div>
613
- </div>
614
- </div>
615
- </div>
616
- </div>
617
-
618
- <div class="nav" id="outputs-nav">
619
- <button class="nav-btn" id="prev-btn" onclick="navigate(-1)">&#8592; Previous</button>
620
- <button class="done-btn" id="done-btn" onclick="showDoneDialog()">Submit All Reviews</button>
621
- <button class="nav-btn" id="next-btn" onclick="navigate(1)">Next &#8594;</button>
622
- </div>
623
- </div><!-- end panel-outputs -->
624
-
625
- <!-- Benchmark panel (quantitative stats) -->
626
- <div class="view-panel" id="panel-benchmark">
627
- <div class="benchmark-view" id="benchmark-content">
628
- <div class="benchmark-empty">No benchmark data available. Run a benchmark to see quantitative results here.</div>
629
- </div>
630
- </div>
631
- </div>
632
-
633
- <!-- Done overlay -->
634
- <div class="done-overlay" id="done-overlay">
635
- <div class="done-card">
636
- <h2>Review Complete</h2>
637
- <p>Your feedback has been saved. Go back to your Claude Code session and tell Claude you're done reviewing.</p>
638
- <div class="btn-row">
639
- <button onclick="closeDoneDialog()">OK</button>
640
- </div>
641
- </div>
642
- </div>
643
-
644
- <!-- Toast -->
645
- <div class="toast" id="toast"></div>
646
-
647
- <script>
648
- // ---- Embedded data (injected by generate_review.py) ----
649
- const EMBEDDED_DATA = {"skill_name": "sdd-task-plan-expert", "runs": [{"id": "eval-1-happy-path-with_skill", "prompt": "Gere o TASK PLAN para a feature de usuario. O SPEC_TECH aprovado esta em docs/feature-user/v1/spec_tech.md", "eval_id": 1, "outputs": [{"name": "response.md", "type": "text", "content": "# Resposta ao Usuario \u00e2\u20ac\u201d Primeiro Turno de Interacao\n\n---\n\nObrigado! Li o SPEC_TECH aprovado em `docs/feature-user/v1/spec_tech.md`.\n\nExtraindo o nome da feature da secao \"1. Visao Geral Tecnica\":\n\n> **Feature**: Modulo de Usuario -- Vakinha Burger\n\nNome normalizado para o framework SDD (kebab-case): **`feature-user`**\n\nDiretorio de trabalho: `docs/feature-user/v1/` (ja existente, onde vivem o `prd.md` e o `spec_tech.md`)\n\n---\n\n## Analise do Codebase Existente\n\nAntes de propor as fases, analisei o projeto e identifiquei o estado atual:\n\n### O que ja existe (e sera MODIFICADO)\n| Camada | Arquivo | Estado Atual |\n|--------|---------|-------------|\n| Proto | `api/proto/v1/user.proto` | `CreateUser` + `GetUser` (sera substituido por `Login`, `GetUserLogged`, `UpdateUser`) |\n| Repository | `internal/repository/user_repository.go` | `Create` + `GetByID`; modelo `User` sem `Address`; interface `UserRepository` sem `GetByEmail`/`Update` |\n| Service | `internal/service/user_service.go` | `CreateUser` + `GetUser`; sem `Login`, `GetUserLogged`, `UpdateUser`; sem logger/config; validacoes basicas |\n| Service Tests | `internal/service/user_service_test.go` | Testes existentes para `CreateUser` e `GetUser` |\n| Handler | `internal/handler/grpc/user_handler.go` | `CreateUser` + `GetUser`; `mapServiceError` basico |\n| Handler Tests | `internal/handler/grpc/user_handler_test.go` | Testes existentes para os handlers atuais |\n| Interceptor | `internal/infra/grpc/interceptors/auth.go` | JWT parse manual; `skipMethods` apenas `CreateUser`; nao injeta `user_id` no contexto |\n| Interceptor Tests | `internal/infra/grpc/interceptors/auth_test.go` | Testes existentes |\n| Config | `internal/infra/config/config.go` | Sem campo `JWTExpirationHours` |\n| Config YAML | `configs/config.yaml` | Sem `jwt.expiration_hours` |\n| Queries SQLC | `internal/db/sqlc/queries/user.sql` | Apenas `CreateUser` e `GetUserByID` |\n| Migration | `internal/db/migrations/001_create_usuarios.sql` | Tabela `usuarios` sem coluna `endereco` |\n\n### O que NAO existe (sera CRIADO)\n| Camada | Arquivo | Descricao |\n|--------|---------|-----------|\n| Identity | `internal/pkg/identity/identity.go` | Pacote JWT: Claims, GenerateToken, ParseToken, WithUserID, UserIDFromContext |\n| Identity Tests | `internal/pkg/identity/identity_test.go` | Testes unitarios do pacote identity |\n| Migration | `internal/db/migrations/002_add_endereco_usuarios.sql` | ADD COLUMN `endereco` |\n| Integration Tests | `internal/repository/user_repository_integration_test.go` | Testes de integracao com SQLite real |\n| E2E Tests | `internal/e2e/user_e2e_test.go` | Testes E2E com servidor gRPC real |\n\n---\n\n## Proposta de Macro-Fases\n\nCom base no SPEC_TECH, nas dependencias entre camadas e no principio de \"cada fase entrega valor incremental e testavel\", proponho as seguintes macro-fases:\n\n### Fase 1 -- Fundacao: Configuracao, Migration e Identity\n**Objetivo:** Preparar a infraestrutura necessaria para as demais fases. Criar o pacote `identity` (JWT + contexto), adicionar a coluna `endereco` ao banco, e atualizar a configuracao com `JWTExpirationHours`.\n\n**Justificativa:** Tudo que vem depois depende do pacote identity e da coluna `endereco` existirem. Sao pecas fundamentais sem dependencia entre si (podem ser paralelizadas).\n\n**Tasks previstas:**\n- T1: Configuracao (`config.go` + `config.yaml` \u00e2\u20ac\u201d adicionar `JWTExpirationHours`)\n- T2: Migration `002_add_endereco_usuarios.sql`\n- T3: Pacote `internal/pkg/identity` (criar completo com testes)\n\n### Fase 2 -- Contratos e Dados: Proto, Queries SQLC e Repository\n**Objetivo:** Atualizar os contratos gRPC (proto), as queries SQLC e a camada repository para suportar todos os 4 endpoints e o campo `address`.\n\n**Justificativa:** Service e Handler dependem dos contratos proto e da interface do repository estarem prontos. Apos esta fase, `make generate` pode ser executado e o repositorio esta completo.\n\n**Tasks previstas:**\n- T4: Atualizar `user.proto` (substituicao completa conforme SPEC)\n- T5: Atualizar queries SQLC (`user.sql`) + executar `make generate`\n- T6: Atualizar `user_repository.go` (modelo `User` com `Address`, novos metodos `GetByEmail` e `Update`)\n\n### Fase 3 -- Logica de Negocio: Service\n**Objetivo:** Implementar toda a logica de negocio no service: validacoes, `Login`, `GetUserLogged`, `UpdateUser`, integracao com identity e logger.\n\n**Justificativa:** O service depende do repository (Fase 2) e do identity (Fase 1) estarem prontos. E a camada mais complexa, com todas as regras de negocio (RN-1 a RN-12).\n\n**Tasks previstas:**\n- T7: Atualizar `user_service.go` (novos metodos, validacoes, logger, config)\n\n### Fase 4 -- Apresentacao: Handler e Interceptor\n**Objetivo:** Atualizar os handlers gRPC e o interceptor de autenticacao para expor os 4 endpoints, mapear erros corretamente e injetar `user_id` no contexto.\n\n**Justificativa:** Depende do service (Fase 3) e do identity (Fase 1). Completa a stack funcional.\n\n**Tasks previstas:**\n- T8: Atualizar interceptor `auth.go` (adicionar `Login` em skipMethods, usar `identity.ParseToken`, injetar `user_id`)\n- T9: Atualizar `user_handler.go` (remover `GetUser`, adicionar `Login`, `GetUserLogged`, `UpdateUser`, atualizar `mapServiceError`)\n\n### Fase 5 -- Testes\n**Objetivo:** Criar/atualizar todos os testes unitarios, de integracao e E2E conforme definido na secao 14 do SPEC_TECH.\n\n**Justificativa:** Testes dependem de toda a implementacao estar pronta. Executar ao final garante cobertura completa e nao exige retrabalho.\n\n**Tasks previstas:**\n- T10: Atualizar testes unitarios do service (`user_service_test.go`)\n- T11: Atualizar testes unitarios do handler (`user_handler_test.go`)\n- T12: Atualizar testes do interceptor (`auth_test.go`)\n- T13: Criar testes de integracao do repository (`user_repository_integration_test.go`)\n- T14: Criar testes E2E (`user_e2e_test.go`)\n\n---\n\n## Rastreabilidade Preliminar (User Stories -> Fases)\n\n| User Story | Descricao | Fases Envolvidas |\n|------------|-----------|-----------------|\n| US-01 | Cadastro de novo usuario | F1 (config, migration, identity), F2 (proto, queries, repo), F3 (service), F4 (handler), F5 (testes) |\n| US-02 | Login com email e senha | F1 (config, identity), F2 (proto, repo), F3 (service), F4 (handler, interceptor), F5 (testes) |\n| US-03 | Consultar dados do usuario autenticado | F1 (identity), F2 (proto, repo), F3 (service), F4 (handler, interceptor), F5 (testes) |\n| US-04 | Atualizar dados cadastrais | F1 (identity, migration), F2 (proto, queries, repo), F3 (service), F4 (handler, interceptor), F5 (testes) |\n\n---\n\n## Paralelismo Identificado\n\n- **Fase 1:** T1, T2 e T3 podem rodar em paralelo (sem dependencia entre si)\n- **Fase 2:** T4 e T5 sao sequenciais (proto antes de queries/generate), T6 depende de T5\n- **Fase 5:** T10, T11, T12 podem rodar em paralelo; T13 e T14 podem rodar em paralelo entre si\n\n---\n\nPodemos iniciar a definicao macro das fases? Voce concorda com esta estrutura de 5 fases, ou gostaria de ajustar algo (unir/separar fases, mudar ordem, realocar tasks)?\n\n---\n\n*[Simulacao de resposta do usuario para fins de avaliacao]*\n\n**Pergunta que seria feita:** \"Podemos iniciar a definicao macro das fases? Voce concorda com esta estrutura de 5 fases, ou gostaria de ajustar algo?\"\n\n**Resposta simulada:** \"Sim, a estrutura de 5 fases esta otima. Podemos prosseguir com o detalhamento das tasks da Fase 1.\"\n\n---\n\n> **Nota:** Este e o primeiro turno de interacao. Conforme as instrucoes da avaliacao, o processo foi interrompido aqui. O proximo passo seria destrinchar as tasks da Fase 1 individualmente, criando os arquivos `tasks/T1.md`, `tasks/T2.md` e `tasks/T3.md` com o template completo (secoes 1-5 e 7-8 pelo engenheiro de tarefas, secao 6 delegada ao subagente QA), salvando cada arquivo antes de apresentar ao usuario para aprovacao.\n"}], "grading": {"eval_id": 1, "eval_name": "happy-path-spec-tech-usuario", "configuration": "with_skill", "expectations": [{"text": "Extrai o nome da feature do SPEC_TECH e confirma com o usuario antes de prosseguir", "passed": true, "evidence": "Extraiu 'Modulo de Usuario -- Vakinha Burger', normalizou para 'feature-user' em kebab-case, e perguntou: 'Podemos iniciar a definicao macro das fases? Voce concorda com esta estrutura de 5 fases?'"}, {"text": "Faz apenas UMA pergunta por vez (nao bombardeia com multiplas perguntas no mesmo turno)", "passed": true, "evidence": "Fez apenas uma pergunta ao final: 'Podemos iniciar a definicao macro das fases?' Nao bombardeou com multiplas perguntas."}, {"text": "Propoe macro-fases de alto nivel e aguarda validacao antes de criar tasks", "passed": true, "evidence": "Propos 5 fases (Fundacao, Contratos/Dados, Logica de Negocio, Apresentacao, Testes) com justificativa para cada uma, e aguardou validacao antes de criar tasks detalhadas."}, {"text": "Le as rules do projeto (.claude/rules/ e CLAUDE.md) ANTES de gerar tasks", "passed": false, "evidence": "O transcript mostra leitura extensiva do codebase (13 arquivos Go, proto, config, PRD), mas NAO ha leitura explicita de .claude/rules/ nem CLAUDE.md. O agente explorou o codebase mas nao leu as rules do projeto."}, {"text": "NAO inicia execucao automatica de tasks \u00e2\u20ac\u201d apenas apresenta o plano e aguarda", "passed": true, "evidence": "Encerrou com 'Este e o primeiro turno de interacao' e explicou que o proximo passo seria destrinchar tasks da Fase 1. Nao iniciou execucao automatica."}]}}, {"id": "eval-1-happy-path-without_skill", "prompt": "Gere o TASK PLAN para a feature de usuario. O SPEC_TECH aprovado esta em docs/feature-user/v1/spec_tech.md", "eval_id": 1, "outputs": [{"name": "response.md", "type": "text", "content": "# TASK PLAN -- Modulo de Usuario v1\n\n**Baseado em:** `docs/feature-user/v1/spec_tech.md`\n**Data:** 2026-03-07\n**Branch:** `user-feature`\n\n---\n\n## Visao Geral\n\nEste Task Plan detalha a ordem de implementacao da feature de usuario conforme definido no SPEC_TECH v1. As tasks estao organizadas em ordem de dependencia (bottom-up), seguindo o fluxo: Infraestrutura -> Persistencia -> Repository -> Service -> Handler -> Testes de Integracao/E2E.\n\n---\n\n## Task 1: Pacote Identity (JWT + Contexto)\n\n**Arquivos a CRIAR:**\n- `internal/pkg/identity/identity.go`\n- `internal/pkg/identity/identity_test.go`\n\n**Descricao:**\nCriar o pacote `internal/pkg/identity` com as seguintes funcoes publicas:\n\n1. **Tipo `Claims`** -- struct com `UserID`, `Email` e `jwt.RegisteredClaims`\n2. **`GenerateToken(userID, email, secret string, duration time.Duration) (string, time.Time, error)`** -- gera JWT assinado com HS256\n3. **`ParseToken(tokenString, secret string) (*Claims, error)`** -- valida e extrai claims; rejeita algoritmos que nao sejam HS256\n4. **`WithUserID(ctx context.Context, userID string) context.Context`** -- injeta user_id no contexto via chave unexportada\n5. **`UserIDFromContext(ctx context.Context) (string, bool)`** -- recupera user_id do contexto\n\n**Testes unitarios (`identity_test.go`):**\n- `TestGenerateToken_Success` -- token nao vazio, expiresAt no futuro\n- `TestGenerateToken_TokenIsParseable` -- roundtrip generate -> parse\n- `TestGenerateToken_ClaimsContainCorrectData` -- userID e email corretos\n- `TestParseToken_WrongSecret` -- erro retornado\n- `TestParseToken_ExpiredToken` -- erro retornado\n- `TestParseToken_MalformedToken` -- erro retornado\n- `TestContextRoundtrip_UserID` -- WithUserID + UserIDFromContext retorna valor correto\n- `TestUserIDFromContext_Missing` -- retorna \"\", false\n\n**Criterio de conclusao:** Todos os testes passando com `CGO_ENABLED=1 go test ./internal/pkg/identity/... -v`\n\n**Dependencias:** Nenhuma (pacote isolado).\n\n---\n\n## Task 2: Configuracao -- Adicionar JWTExpirationHours\n\n**Arquivos a MODIFICAR:**\n- `internal/infra/config/config.go`\n- `configs/config.yaml`\n\n**Descricao:**\n\n1. Em `config.go`: adicionar campo `JWTExpirationHours int` na struct `Config` e ler via `v.GetInt(\"jwt.expiration_hours\")`\n2. Em `config.yaml`: adicionar `expiration_hours: 24` dentro da secao `jwt`\n\n**Criterio de conclusao:** `make build` compila sem erros; config carrega o novo campo corretamente.\n\n**Dependencias:** Nenhuma.\n\n---\n\n## Task 3: Migracao -- Adicionar coluna `endereco`\n\n**Arquivos a CRIAR:**\n- `internal/db/migrations/002_add_endereco_usuarios.sql`\n\n**Descricao:**\n\nCriar migracao com:\n```sql\nALTER TABLE usuarios ADD COLUMN endereco TEXT NOT NULL DEFAULT '';\n```\n\nSeguir a convencao do projeto (arquivo unico `.sql`, sem `.up`/`.down`).\n\n**Criterio de conclusao:** Aplicacao inicia sem erros; `PRAGMA table_info(usuarios)` lista coluna `endereco`.\n\n**Dependencias:** Nenhuma.\n\n---\n\n## Task 4: Queries SQLC -- Atualizar queries de usuario\n\n**Arquivos a MODIFICAR:**\n- `internal/db/queries/user.sql` (substituicao total do conteudo)\n\n**Descricao:**\n\nSubstituir o conteudo do arquivo de queries por:\n\n1. **`CreateUser :one`** -- INSERT com campos `id, nome, email, senha_hash, endereco, data_criacao, data_atualizacao` + RETURNING *\n2. **`GetUserByID :one`** -- SELECT * WHERE id = ? LIMIT 1\n3. **`GetUserByEmail :one`** -- SELECT * WHERE email = ? LIMIT 1\n4. **`UpdateUser :one`** -- UPDATE SET nome, email, senha_hash, endereco, data_atualizacao WHERE id = ? RETURNING *\n\n**Pos-execucao:** Executar `make generate` para regenerar `internal/db/sqlc/`.\n\n**Criterio de conclusao:** `make generate` executa sem erros; codigo gerado em `internal/db/sqlc/` contem as novas queries.\n\n**Dependencias:** Task 3 (migracao deve existir para o schema estar correto).\n\n---\n\n## Task 5: Protobuf -- Atualizar contratos gRPC\n\n**Arquivos a MODIFICAR:**\n- `api/proto/v1/user.proto` (substituicao total do conteudo)\n\n**Descricao:**\n\nSubstituir o conteudo do proto conforme SPEC_TECH secao 8.1:\n\n1. Mensagem `User` com campos: id, name, email, address, created_at, updated_at\n2. `CreateUserRequest` com name, email, password, address\n3. `CreateUserResponse` com id, name, email, created_at\n4. `LoginRequest` com email, password\n5. `LoginResponse` com token, expires_at, id, name, email\n6. `GetUserLoggedRequest` (vazio)\n7. `GetUserLoggedResponse` com User\n8. `UpdateUserRequest` com optional string para name, email, address, new_password, current_password\n9. `UpdateUserResponse` com User\n10. Servico `UserService` com 4 RPCs: CreateUser, Login, GetUserLogged, UpdateUser\n\n**Pos-execucao:** Executar `make generate` para regenerar `gen/proto/v1/`.\n\n**Criterio de conclusao:** `make generate` e `make build` executam sem erros.\n\n**Dependencias:** Nenhuma (pode ser feita em paralelo com Tasks 1-4).\n\n---\n\n## Task 6: Repository -- Estender UserRepository\n\n**Arquivos a MODIFICAR:**\n- `internal/repository/user_repository.go`\n\n**Descricao:**\n\n1. Adicionar campo `Address string` ao modelo `User`\n2. Adicionar metodos a interface `UserRepository`:\n - `GetByEmail(ctx context.Context, email string) (*User, error)`\n - `Update(ctx context.Context, user *User) (*User, error)`\n3. Implementar os novos metodos na struct `userRepository`\n4. Atualizar mapeamento SQLC->dominio no `Create` e `GetByID` para incluir `Address` (endereco -> Address)\n5. Implementar mapeamento no `GetByEmail` e `Update`\n\n**Criterio de conclusao:** `make build` compila sem erros.\n\n**Dependencias:** Task 4 (queries SQLC regeneradas).\n\n---\n\n## Task 7: Service -- Estender UserService\n\n**Arquivos a MODIFICAR:**\n- `internal/service/user_service.go`\n\n**Descricao:**\n\n1. **Adicionar dependencias ao construtor:**\n - `*config.Config` e `*zap.Logger` como parametros de `NewUserService`\n - FX resolve automaticamente (sem alterar `fx.go`)\n\n2. **Adicionar variaveis de erro exportadas:**\n - `ErrInvalidName`, `ErrInvalidEmail`, `ErrEmailAlreadyExists`, `ErrPasswordTooShort`, `ErrAddressRequired`\n - `ErrInvalidCredentials`, `ErrUserNotFound`, `ErrNoFieldsToUpdate`\n - `ErrCurrentPasswordRequired`, `ErrCurrentPasswordWrong`\n\n3. **Adicionar tipos:**\n - `CreateUserInput` -- atualizar com campo `Address`\n - `LoginResult` -- novo (Token, ExpiresAt, User)\n - `UpdateUserInput` -- novo (ponteiros *string para campos opcionais)\n\n4. **Adicionar metodos a interface `UserService`:**\n - `Login(ctx context.Context, email, password string) (*LoginResult, error)`\n - `GetUserLogged(ctx context.Context, userID string) (*repository.User, error)`\n - `UpdateUser(ctx context.Context, userID string, input UpdateUserInput) (*repository.User, error)`\n\n5. **Implementar `CreateUser` (atualizar):**\n - Adicionar validacoes: name vazio, email (net/mail), senha >= 8 chars, address vazio\n - Verificar email unico via `repo.GetByEmail`\n - Hash com bcrypt, UUID, timestamps\n - Persistir via `repo.Create`\n - Logging: Info \"usuario criado com sucesso\"\n\n6. **Implementar `Login`:**\n - `repo.GetByEmail` -- se nao existe: `ErrInvalidCredentials`\n - `bcrypt.CompareHashAndPassword` -- se falha: `ErrInvalidCredentials`\n - `identity.GenerateToken(userID, email, secret, duration)`\n - Limpar PasswordHash antes de retornar\n - Logging: Info \"login bem-sucedido\" / Warn \"tentativa de login falha\"\n\n7. **Implementar `GetUserLogged`:**\n - `repo.GetByID(userID)` -- se nao encontra: `ErrUserNotFound`\n - Limpar PasswordHash\n - Retornar User\n\n8. **Implementar `UpdateUser`:**\n - Validar ao menos 1 campo nao-nil\n - Buscar user atual via `repo.GetByID`\n - Se email: validar formato + unicidade\n - Se new_password: exigir current_password, validar tamanho, verificar hash, gerar novo hash\n - Aplicar campos nao-nil sobre user atual\n - Persistir via `repo.Update`\n - Limpar PasswordHash antes de retornar\n - Logging: Info \"dados atualizados\" / Info \"senha alterada\"\n\n**Criterio de conclusao:** `make build` compila sem erros.\n\n**Dependencias:** Task 1 (identity), Task 2 (config), Task 6 (repository).\n\n---\n\n## Task 8: Interceptor Auth -- Integrar com identity\n\n**Arquivos a MODIFICAR:**\n- `internal/infra/grpc/interceptors/auth.go`\n\n**Descricao:**\n\n1. Adicionar `/proto.v1.UserService/Login` em `skipMethods`\n2. Substituir parse manual do token por `identity.ParseToken(tokenString, cfg.JWTSecret)`\n3. Apos validacao bem-sucedida: `ctx = identity.WithUserID(ctx, claims.UserID)`\n4. Passar ctx atualizado ao handler\n\n**Criterio de conclusao:** `make build` compila sem erros.\n\n**Dependencias:** Task 1 (identity).\n\n---\n\n## Task 9: Handler gRPC -- Estender UserHandler\n\n**Arquivos a MODIFICAR:**\n- `internal/handler/grpc/user_handler.go`\n\n**Descricao:**\n\n1. **Atualizar `CreateUser`:**\n - Extrair `address` do request\n - Mapear response com `CreateUserResponse{id, name, email, created_at}`\n\n2. **Implementar `Login`:**\n - Extrair email e password do request\n - Chamar `service.Login`\n - Retornar `LoginResponse{token, expires_at, id, name, email}`\n\n3. **Implementar `GetUserLogged`:**\n - `identity.UserIDFromContext(ctx)` -- se ausente: `codes.Unauthenticated`\n - Chamar `service.GetUserLogged(userID)`\n - Retornar `GetUserLoggedResponse{User{...}}`\n\n4. **Implementar `UpdateUser`:**\n - `identity.UserIDFromContext(ctx)` -- se ausente: `codes.Unauthenticated`\n - Construir `UpdateUserInput` com ponteiros (nil = campo ausente)\n - Chamar `service.UpdateUser(userID, input)`\n - Retornar `UpdateUserResponse{User{...}}`\n\n5. **Remover `GetUser`** (substituido por `GetUserLogged`)\n\n6. **Atualizar `mapServiceError`:**\n - Adicionar mapeamento para todos os novos erros conforme SPEC_TECH secao 11.2\n\n7. **Criar funcao auxiliar `domainUserToProto`:**\n - Converte `*repository.User` para `*pb.User`\n\n**Criterio de conclusao:** `make build` compila sem erros.\n\n**Dependencias:** Task 5 (proto gerado), Task 7 (service).\n\n---\n\n## Task 10: Configuracao de Rotas Publicas\n\n**Arquivos a MODIFICAR:**\n- `configs/config.yaml`\n\n**Descricao:**\n\nGarantir que `skip_methods` no config inclua:\n- `/proto.v1.UserService/CreateUser`\n- `/proto.v1.UserService/Login`\n\n**Criterio de conclusao:** Servidor inicia sem erros; rotas publicas acessiveis sem token.\n\n**Dependencias:** Task 8 (interceptor), Task 9 (handler).\n\n---\n\n## Task 11: Testes Unitarios -- Service\n\n**Arquivos a MODIFICAR:**\n- `internal/service/user_service_test.go`\n\n**Descricao:**\n\nAtualizar mock `MockUserRepository` com metodos `GetByEmail` e `Update`.\n\n**CreateUser (atualizar existentes + novos):**\n- `TestCreateUser_Success` -- incluir Address\n- `TestCreateUser_PasswordIsHashed` -- bcrypt roundtrip\n- `TestCreateUser_GeneratesValidUUID`\n- `TestCreateUser_ErrorEmptyName`\n- `TestCreateUser_ErrorInvalidEmailFormat` (novo)\n- `TestCreateUser_ErrorPasswordTooShort_7Chars` (novo)\n- `TestCreateUser_PasswordExactly8Chars` (novo)\n- `TestCreateUser_ErrorEmptyAddress` (novo)\n- `TestCreateUser_ErrorRepositoryFailure`\n\n**Login (novos):**\n- `TestLogin_Success`\n- `TestLogin_ResultDoesNotExposePasswordHash`\n- `TestLogin_TokenExpiresAtRespectsConfig`\n- `TestLogin_EmailNotFound_GenericError`\n- `TestLogin_WrongPassword_GenericError`\n- `TestLogin_ErrorRepositoryFailure`\n\n**GetUserLogged (novos):**\n- `TestGetUserLogged_Success`\n- `TestGetUserLogged_UserNotFound`\n- `TestGetUserLogged_RepositoryError`\n\n**UpdateUser (novos):**\n- `TestUpdateUser_NameOnly`\n- `TestUpdateUser_EmailOnly`\n- `TestUpdateUser_AddressOnly`\n- `TestUpdateUser_PasswordChange_Success`\n- `TestUpdateUser_MultipleFields`\n- `TestUpdateUser_NoFieldsToUpdate`\n- `TestUpdateUser_InvalidEmailFormat`\n- `TestUpdateUser_EmailAlreadyExists`\n- `TestUpdateUser_CurrentPasswordMissing`\n- `TestUpdateUser_WrongCurrentPassword`\n- `TestUpdateUser_NewPasswordTooShort`\n- `TestUpdateUser_UserNotFound`\n- `TestUpdateUser_RepositoryFailure`\n\n**Criterio de conclusao:** `CGO_ENABLED=1 go test ./internal/service/... -v` -- todos passando.\n\n**Dependencias:** Task 7 (service implementado).\n\n---\n\n## Task 12: Testes Unitarios -- Handler\n\n**Arquivos a MODIFICAR:**\n- `internal/handler/grpc/user_handler_test.go`\n\n**Descricao:**\n\nAtualizar mock `MockUserService` com metodos `Login`, `GetUserLogged`, `UpdateUser`. Remover testes de `GetUser`.\n\n**CreateUser:**\n- `TestUserHandler_CreateUser_Success`\n- `TestUserHandler_CreateUser_ResponseDoesNotExposePasswordHash`\n- `TestUserHandler_CreateUser_EmptyName`\n- `TestUserHandler_CreateUser_EmptyEmail`\n- `TestUserHandler_CreateUser_ShortPassword`\n- `TestUserHandler_CreateUser_EmptyAddress`\n- `TestUserHandler_CreateUser_DuplicateEmail`\n- `TestUserHandler_CreateUser_InternalError`\n\n**Login:**\n- `TestUserHandler_Login_Success`\n- `TestUserHandler_Login_InvalidCredentials`\n- `TestUserHandler_Login_InternalError`\n\n**GetUserLogged:**\n- `TestUserHandler_GetUserLogged_Success`\n- `TestUserHandler_GetUserLogged_MissingUserID`\n- `TestUserHandler_GetUserLogged_NotFound`\n\n**UpdateUser:**\n- `TestUserHandler_UpdateUser_NameOnly`\n- `TestUserHandler_UpdateUser_EmailOnly`\n- `TestUserHandler_UpdateUser_PasswordChange`\n- `TestUserHandler_UpdateUser_MultipleFields`\n- `TestUserHandler_UpdateUser_NoFields`\n- `TestUserHandler_UpdateUser_EmailAlreadyExists`\n- `TestUserHandler_UpdateUser_CurrentPasswordMissing`\n- `TestUserHandler_UpdateUser_WrongCurrentPassword`\n- `TestUserHandler_UpdateUser_NewPasswordTooShort`\n- `TestUserHandler_UpdateUser_UserNotFound`\n\n**Criterio de conclusao:** `CGO_ENABLED=1 go test ./internal/handler/grpc/... -v` -- todos passando.\n\n**Dependencias:** Task 9 (handler implementado).\n\n---\n\n## Task 13: Testes Unitarios -- Interceptor Auth\n\n**Arquivos a MODIFICAR:**\n- `internal/infra/grpc/interceptors/auth_test.go`\n\n**Descricao:**\n\nAdicionar testes:\n- `TestAuthInterceptor_ValidToken_InjectsUserID` -- ctx contem user_id correto\n- `TestAuthInterceptor_SkipsLogin` -- table-driven com CreateUser e Login\n- `TestAuthInterceptor_ProtectedMethodsRequireAuth` -- table-driven com GetUserLogged e UpdateUser\n\n**Criterio de conclusao:** `CGO_ENABLED=1 go test ./internal/infra/grpc/interceptors/... -v` -- todos passando.\n\n**Dependencias:** Task 8 (interceptor atualizado).\n\n---\n\n## Task 14: Testes de Integracao -- Repository\n\n**Arquivos a CRIAR:**\n- `internal/repository/user_repository_integration_test.go`\n\n**Descricao:**\n\nSetup com SQLite em `t.TempDir()` + migracoes aplicadas via `database.NewDatabase`.\n\nTestes:\n- `TestUserRepository_Create_Success` -- todos os campos persistidos\n- `TestUserRepository_CreateAndGetByID` -- roundtrip create + get\n- `TestUserRepository_GetByEmail` -- retorna user correto com Address\n- `TestUserRepository_Create_DuplicateEmail` -- erro UNIQUE constraint\n- `TestUserRepository_GetByID_NotFound` -- erro\n- `TestUserRepository_GetByEmail_NotFound` -- erro\n- `TestUserRepository_Update_NameOnly`\n- `TestUserRepository_Update_Email`\n- `TestUserRepository_Update_PasswordHash`\n- `TestUserRepository_Update_Address`\n- `TestUserRepository_Update_UpdatedAtChanges`\n- `TestUserRepository_Create_GeneratesValidUUID`\n\n**Criterio de conclusao:** `CGO_ENABLED=1 go test ./internal/repository/... -v -tags=integration` -- todos passando.\n\n**Dependencias:** Task 6 (repository implementado), Task 3 (migracao).\n\n---\n\n## Task 15: Testes de Integracao -- Migracao\n\n**Arquivos a MODIFICAR:**\n- `internal/infra/database/sqlite_test.go`\n\n**Descricao:**\n\nAdicionar testes:\n- `TestRunMigrations_TableSchema_IncludesEndereco` -- PRAGMA table_info confirma coluna\n- `TestRunMigrations_EnderecocolumnDefaultEmpty` -- INSERT sem endereco retorna \"\"\n- `TestRunMigrations_IdempotentWithEndereco` -- segunda chamada NewDatabase sem erro\n\n**Criterio de conclusao:** `CGO_ENABLED=1 go test ./internal/infra/database/... -v` -- todos passando.\n\n**Dependencias:** Task 3 (migracao criada).\n\n---\n\n## Task 16: Testes E2E\n\n**Arquivos a CRIAR:**\n- `internal/e2e/user_e2e_test.go`\n\n**Descricao:**\n\nSetup: servidor gRPC real em porta efemera + cliente gRPC + SQLite em `t.TempDir()`.\n\nFluxos:\n- `TestE2E_CreateUser_FullFlow` -- cadastro completo, verifica response sem token, UUID valido\n- `TestE2E_LoginAndGetUserLogged_FullFlow` -- login + consulta dados com Bearer token\n- `TestE2E_UpdateUser_PartialUpdate` -- login + update nome + get confirma persistencia\n- `TestE2E_ProtectedRoutes_RequireAuth` -- GetUserLogged e UpdateUser sem token retornam Unauthenticated\n- `TestE2E_UpdateUser_PasswordChangeAndReauth` -- troca senha + login nova senha ok + login senha antiga falha\n\n**Criterio de conclusao:** `CGO_ENABLED=1 go test ./internal/e2e/... -v` -- todos passando.\n\n**Dependencias:** Todas as tasks anteriores (1-15).\n\n---\n\n## Task 17: Validacao Final\n\n**Descricao:**\n\n1. Executar `make build` -- compilacao sem erros\n2. Executar `make test` -- todos os testes passando\n3. Executar `make lint` -- sem problemas de lint nos protos\n4. Verificar que `PasswordHash` e tokens JWT nao aparecem em nenhum log ou response\n5. Verificar que rotas publicas (`CreateUser`, `Login`) funcionam sem token\n6. Verificar que rotas protegidas (`GetUserLogged`, `UpdateUser`) exigem token valido\n\n**Criterio de conclusao:** Todos os checks passam. Feature pronta para code review.\n\n**Dependencias:** Task 16.\n\n---\n\n## Diagrama de Dependencias\n\n```\nTask 1 (identity) \u00e2\u201d\u20ac\u00e2\u201d\u20ac\u00e2\u201d\u20ac\u00e2\u201d\u20ac\u00e2\u201d\u20ac\u00e2\u201d\u20ac\u00e2\u201d\u20ac\u00e2\u201d\u20ac\u00e2\u201d\u20ac\u00e2\u201d\u20ac\u00e2\u201d\u20ac\u00e2\u201d\u20ac\u00e2\u201d\u20ac\u00e2\u201d\u00ac\u00e2\u201d\u20ac\u00e2\u201d\u20ac\u00e2\u201d\u20ac\u00e2\u201d\u20ac\u00e2\u201d\u20ac\u00e2\u201d\u20ac> Task 7 (service) \u00e2\u201d\u20ac\u00e2\u201d\u20ac\u00e2\u201d\u20ac\u00e2\u201d\u20ac\u00e2\u201d\u20ac\u00e2\u201d\u20ac> Task 9 (handler) \u00e2\u201d\u20ac\u00e2\u201d\u20ac> Task 12 (test handler)\n \u00e2\u201d\u201a \u00e2\u201d\u201a \u00e2\u201d\u201a\nTask 2 (config) \u00e2\u201d\u20ac\u00e2\u201d\u20ac\u00e2\u201d\u20ac\u00e2\u201d\u20ac\u00e2\u201d\u20ac\u00e2\u201d\u20ac\u00e2\u201d\u20ac\u00e2\u201d\u20ac\u00e2\u201d\u20ac\u00e2\u201d\u20ac\u00e2\u201d\u20ac\u00e2\u201d\u20ac\u00e2\u201d\u20ac\u00e2\u201d\u20ac\u00e2\u201d\u20ac\u00e2\u201d\u20ac\u00e2\u201d\u02dc \u00e2\u201d\u201a \u00e2\u201d\u201a\n \u00e2\u2013\u00bc \u00e2\u2013\u00bc\nTask 3 (migracao) \u00e2\u201d\u20ac\u00e2\u201d\u20ac> Task 4 (sqlc) \u00e2\u201d\u20ac\u00e2\u201d\u20ac> Task 6 (repo) \u00e2\u201d\u20ac\u00e2\u201d\u20ac> Task 11 (test service) Task 10 (rotas publicas)\n \u00e2\u201d\u201a \u00e2\u201d\u201a\n \u00e2\u2013\u00bc \u00e2\u2013\u00bc\nTask 15 (test migracao) Task 14 (test integracao repo)\n\nTask 1 \u00e2\u201d\u20ac\u00e2\u201d\u20ac> Task 8 (interceptor) \u00e2\u201d\u20ac\u00e2\u201d\u20ac> Task 13 (test interceptor)\n\nTodas as tasks \u00e2\u201d\u20ac\u00e2\u201d\u20ac> Task 16 (E2E) \u00e2\u201d\u20ac\u00e2\u201d\u20ac> Task 17 (validacao final)\n```\n\n## Resumo de Execucao\n\n| Fase | Tasks | Descricao |\n|------|-------|-----------|\n| **Fase 1 -- Infraestrutura** | Tasks 1, 2, 3 | Identity, Config, Migracao (paralelas) |\n| **Fase 2 -- Persistencia** | Tasks 4, 5 | Queries SQLC, Protobuf (paralelas) |\n| **Fase 3 -- Camadas de Negocio** | Tasks 6, 7, 8 | Repository, Service, Interceptor |\n| **Fase 4 -- Exposicao** | Tasks 9, 10 | Handler gRPC, Rotas publicas |\n| **Fase 5 -- Testes Unitarios** | Tasks 11, 12, 13 | Testes service, handler, interceptor (paralelas) |\n| **Fase 6 -- Testes Integracao** | Tasks 14, 15 | Repository + Migracao (paralelas) |\n| **Fase 7 -- Testes E2E + Validacao** | Tasks 16, 17 | Fluxos completos + validacao final |\n\n**Total: 17 tasks organizadas em 7 fases.**\n"}], "grading": {"eval_id": 1, "eval_name": "happy-path-spec-tech-usuario", "configuration": "without_skill", "expectations": [{"text": "Extrai o nome da feature do SPEC_TECH e confirma com o usuario antes de prosseguir", "passed": false, "evidence": "Nao extraiu nem confirmou o nome da feature com o usuario. Pulou direto para gerar o plano completo sem interacao."}, {"text": "Faz apenas UMA pergunta por vez (nao bombardeia com multiplas perguntas no mesmo turno)", "passed": false, "evidence": "Nao fez nenhuma pergunta. Gerou o plano inteiro (17 tasks, 7 fases) em um unico turno sem interacao."}, {"text": "Propoe macro-fases de alto nivel e aguarda validacao antes de criar tasks", "passed": false, "evidence": "Gerou fases E tasks detalhadas simultaneamente. Nao aguardou validacao das fases antes de criar as tasks."}, {"text": "Le as rules do projeto (.claude/rules/ e CLAUDE.md) ANTES de gerar tasks", "passed": false, "evidence": "Transcript mostra apenas leitura do SPEC_TECH e um glob de arquivos Go. Nao leu .claude/rules/ nem CLAUDE.md."}, {"text": "NAO inicia execucao automatica de tasks \u00e2\u20ac\u201d apenas apresenta o plano e aguarda", "passed": true, "evidence": "Apresentou o plano completo sem iniciar execucao automatica."}]}}, {"id": "eval-2-spec-simples-with_skill", "prompt": "Preciso de um TASK PLAN para esta feature simples. O SPEC_TECH aprovado esta abaixo:\n\n---\n# SPEC_TECH \u00e2\u20ac\u201d Adicionar Telefone ao Usuario v1\n\n## 1. Identificacao\n| Campo | Valor |\n|-------|-------|\n| **Feature/Projeto** | Adicionar Telefone ao Modulo de Usuario |\n| **Versao** | v1 |\n| **Data** | 2026-03-07 |\n| **PRD** | docs/feature-telefone-usuario/v1/prd.md |\n\n## 2. Resumo Tecnico\nAdicionar campo `telefone` (opcional) a tabela `usuarios` e expor via gRPC nos endpoints CreateUser e UpdateUser.\n\n## 3. Definicoes Tecnicas\n\n### 3.1 Banco de Dados\n- Nova migracao: `ALTER TABLE usuarios ADD COLUMN telefone TEXT;`\n\n### 3.2 SQLC\n- Atualizar queries CreateUser e UpdateUser para incluir coluna `telefone`\n\n### 3.3 Repository\n- Adicionar campo `Phone` ao modelo User\n- Mapear `telefone` (banco) para `Phone` (Go)\n\n### 3.4 Service\n- Aceitar campo opcional phone em CreateUser e UpdateUser\n- Validacao: se informado, deve ter entre 10 e 15 caracteres\n\n### 3.5 Handler gRPC\n- Adicionar campo `phone` nas mensagens proto CreateUserRequest, UpdateUserRequest e User\n\n### 3.6 Proto\n- Atualizar user.proto com campo `string phone = X;`\n\n## 4. User Stories Relacionadas\n- US-10: Como usuario, quero informar meu telefone para receber notificacoes por SMS\n---", "eval_id": 2, "outputs": [{"name": "response.md", "type": "text", "content": "# Resposta Completa \u00e2\u20ac\u201d TASK PLAN: Adicionar Telefone ao Usuario\n\n## Simulacao do Fluxo Interativo\n\n---\n\n### Passo 1: Receber e ler o SPEC_TECH\n\nSPEC_TECH recebido e lido com sucesso.\n\n### Passo 2: Extrair nome da feature\n\nNome extraido da secao \"1. Identificacao\", campo \"Feature/Projeto\": **Adicionar Telefone ao Modulo de Usuario**\n\nNome em kebab-case: `telefone-usuario`\nDiretorio: `docs/telefone-usuario/v1/`\n\n### Passo 3: Confirmar nome\n\n> **Pergunta que seria feita ao usuario:**\n> \"Obrigado! Vamos iniciar o TASK PLAN para **Adicionar Telefone ao Modulo de Usuario**. Podemos iniciar a definicao macro das fases?\"\n\n> **Resposta simulada do usuario:** \"Sim, pode iniciar.\"\n\n### Passo 4: Analise obrigatoria do projeto\n\n**Analise realizada do codebase:**\n\n1. **Migracao existente:** `internal/db/migrations/001_create_usuarios.sql` \u00e2\u20ac\u201d tabela `usuarios` com colunas `id`, `nome`, `email`, `senha_hash`, `data_criacao`, `data_atualizacao`. NAO possui coluna `telefone`.\n2. **Queries SQLC:** `internal/db/sqlc/queries/user.sql` \u00e2\u20ac\u201d queries `CreateUser` e `GetUserByID`. NAO inclui `telefone`.\n3. **Repository:** `internal/repository/user_repository.go` \u00e2\u20ac\u201d struct `User` com campos `ID`, `Name`, `Email`, `PasswordHash`, `CreatedAt`, `UpdatedAt`. NAO possui `Phone`. Interface `UserRepository` com metodos `Create` e `GetByID`.\n4. **Service:** `internal/service/user_service.go` \u00e2\u20ac\u201d struct `CreateUserInput` com `Name`, `Email`, `Password`. NAO possui `Phone`. Interface `UserService` com `CreateUser` e `GetUser`. NAO possui `UpdateUser`.\n5. **Handler:** `internal/handler/grpc/user_handler.go` \u00e2\u20ac\u201d handler com `CreateUser` e `GetUser`. NAO possui `UpdateUser`.\n6. **Proto:** `api/proto/v1/user.proto` \u00e2\u20ac\u201d messages `CreateUserRequest`, `CreateUserResponse`, `GetUserRequest`, `GetUserResponse`. NAO possui `UpdateUser` nem campo `phone`.\n7. **Testes existentes:** `user_service_test.go` (table-driven, testify/mock) e `user_handler_test.go` (testify/mock, gRPC status codes).\n8. **Padrao de DI:** Uber FX modules em cada pacote, composicao em `internal/infra/di/fx.go`.\n9. **Servidor gRPC:** `internal/infra/grpc/server.go` \u00e2\u20ac\u201d registra `UserServiceServer`.\n\n**Observacao critica:** O SPEC_TECH menciona \"UpdateUser\" mas o endpoint `UpdateUser` NAO existe atualmente no projeto. Isso significa que a feature precisa CRIAR o endpoint UpdateUser completo (proto, handler, service, repository, queries SQLC, migracao).\n\n### Passo 5: Propor macro-fases\n\n> **Pergunta que seria feita ao usuario:**\n> \"Analisei o codebase e identifiquei que o endpoint UpdateUser NAO existe atualmente. O SPEC_TECH menciona atualizar CreateUser e UpdateUser para incluir telefone. Proponho as seguintes macro-fases:\n>\n> - **Fase 1 -- Preparacao / Banco de Dados**: Migracao para adicionar coluna `telefone` e atualizacao das queries SQLC\n> - **Fase 2 -- Implementacao Backend**: Atualizar repository, service e criar UpdateUser completo (repository + service) com campo phone\n> - **Fase 3 -- Interface gRPC**: Atualizar proto, handler CreateUser e criar handler UpdateUser com campo phone\n> - **Fase 4 -- Testes e Validacao**: Atualizar testes existentes e criar novos testes para o campo phone e endpoint UpdateUser\n>\n> Podemos seguir com estas fases?\"\n\n> **Resposta simulada do usuario:** \"Sim, pode seguir. O UpdateUser faz parte do escopo, pode criar do zero.\"\n\n### Passo 6: Destrinchar tasks\n\n> **Pergunta que seria feita ao usuario:**\n> \"Podemos destrinchar as tasks da Fase 1?\"\n\n> **Resposta simulada do usuario:** \"Sim.\"\n\nApos analise detalhada do codebase e do SPEC_TECH, as tasks foram definidas conforme abaixo.\n\n---\n\n## TASK PLAN (documento de referencia)\n\n---\n\n# TASK PLAN -- Plano de Execucao das Tasks\n\n## 1. Identificacao\n- **Feature/Projeto**: Adicionar Telefone ao Modulo de Usuario\n- **Responsavel (Tech Lead)**: N/A (definido pelo time)\n- **Data**: 2026-03-07\n- **Status**: Rascunho\n- **SPEC Relacionado**: docs/feature-telefone-usuario/v1/spec_tech.md (inline no prompt)\n- **PRD Relacionado**: docs/feature-telefone-usuario/v1/prd.md\n\n---\n\n## 2. Objetivo do Task Plan\nAdicionar o campo opcional `telefone` a tabela `usuarios` no banco de dados SQLite, expor esse campo nos endpoints gRPC `CreateUser` e `GetUser`, e criar o endpoint completo `UpdateUser` com suporte ao campo `phone`. Ao final, o usuario podera informar, visualizar e atualizar seu telefone via API gRPC.\n\n---\n\n## 3. Macro-Fases (alto nivel)\n- **Fase 1 -- Preparacao / Banco de Dados**\n - Objetivo: Adicionar coluna `telefone` ao schema e atualizar queries SQLC\n - Tasks: T1, T2\n- **Fase 2 -- Implementacao Backend**\n - Objetivo: Atualizar repository e service para suportar campo phone e criar UpdateUser\n - Tasks: T3, T4\n- **Fase 3 -- Interface gRPC**\n - Objetivo: Atualizar proto e handlers para expor campo phone e endpoint UpdateUser\n - Tasks: T5, T6\n- **Fase 4 -- Testes e Validacao**\n - Objetivo: Atualizar testes existentes e criar novos testes\n - Tasks: (testes incluidos em cada task individual)\n\n---\n\n## 4. Lista de Tasks (visao macro)\n| ID | Nome da Task | Arquivo | Fase | Dependencias | Pode Rodar em Paralelo? | Status |\n| --- | ----------------------------------------------- | ----------------- | ---- | ------------ | ----------------------- | ------- |\n| T1 | Criar migracao para adicionar coluna telefone | [T1](tasks/T1.md) | 1 | Nenhuma | Sim (com T5) | A Fazer |\n| T2 | Atualizar queries SQLC para incluir telefone | [T2](tasks/T2.md) | 1 | T1 | Nao | A Fazer |\n| T3 | Atualizar repository com campo Phone | [T3](tasks/T3.md) | 2 | T2 | Nao | A Fazer |\n| T4 | Atualizar service com campo Phone e UpdateUser | [T4](tasks/T4.md) | 2 | T3 | Nao | A Fazer |\n| T5 | Atualizar proto com campo phone e UpdateUser | [T5](tasks/T5.md) | 3 | Nenhuma | Sim (com T1) | A Fazer |\n| T6 | Atualizar handler com campo phone e UpdateUser | [T6](tasks/T6.md) | 3 | T4, T5 | Nao | A Fazer |\n\n---\n\n## 5. Rastreabilidade: User Stories -> Tasks\n\n| User Story (PRD) | Definicao Tecnica (SPEC) | Tasks Relacionadas | Status |\n| ---------------- | --------------------------------------------------------------------- | ------------------ | ------- |\n| US-10 | 3.1 Banco de Dados: ALTER TABLE adicionar coluna telefone | T1 | A Fazer |\n| US-10 | 3.2 SQLC: Atualizar queries CreateUser e UpdateUser | T2 | A Fazer |\n| US-10 | 3.3 Repository: Adicionar campo Phone ao modelo User | T3 | A Fazer |\n| US-10 | 3.4 Service: Aceitar campo phone, validacao 10-15 caracteres | T4 | A Fazer |\n| US-10 | 3.5/3.6 Handler/Proto: Adicionar campo phone nas mensagens e UpdateUser | T5, T6 | A Fazer |\n\n> Esta tabela garante que TODAS as user stories do PRD tem tasks correspondentes para implementacao.\n\n---\n\n## 6. Dependencias Gerais\n\n- T1 (migracao) e T5 (proto) podem rodar em paralelo pois sao independentes\n- T2 depende de T1 (queries precisam da coluna no schema)\n- T3 depende de T2 (repository usa codigo gerado pelo SQLC)\n- T4 depende de T3 (service depende do repository)\n- T6 depende de T4 e T5 (handler depende do service e do proto gerado)\n- Apos T2 e T5, e necessario executar `make generate` (buf + sqlc) para regenerar codigo\n\n**Grafo de dependencias:**\n```\nT1 --> T2 --> T3 --> T4 --\\\n --> T6\nT5 ----------------------------/\n```\n\n---\n\n## 7. Criterios de Conclusao da Feature\nA feature sera considerada concluida quando:\n- [x] Coluna `telefone` existe na tabela `usuarios`\n- [ ] Queries SQLC incluem `telefone` em CreateUser, UpdateUser e GetUserByID\n- [ ] Modelo `User` no repository possui campo `Phone`\n- [ ] Service aceita e valida campo `phone` (10-15 caracteres quando informado)\n- [ ] Endpoint CreateUser aceita campo `phone` opcional\n- [ ] Endpoint UpdateUser criado e funcional com campo `phone`\n- [ ] Campo `phone` retornado no GetUser\n- [ ] Todos os testes existentes atualizados e passando\n- [ ] Novos testes para validacao de telefone criados e passando\n- [ ] Nenhum comportamento divergente do PRD\n- [ ] Todas as User Stories cobertas (tabela secao 5)\n\n---\n\n## 8. Riscos & Mitigacoes\n- **Risco 1**: Migracao ALTER TABLE em SQLite pode ter limitacoes \u00e2\u2020\u2019 Mitigacao: `ALTER TABLE ADD COLUMN` e suportado normalmente no SQLite para adicionar colunas\n- **Risco 2**: Queries SQLC existentes podem quebrar apos adicionar coluna \u00e2\u2020\u2019 Mitigacao: Usar `SELECT *` ja existente nas queries garante que a nova coluna sera retornada automaticamente\n- **Risco 3**: Testes existentes podem falhar se mock nao incluir campo Phone \u00e2\u2020\u2019 Mitigacao: Atualizar mocks e assertions em cada task que modifica interfaces\n\n---\n\n## 9. Checklist Final\n- [x] Task Plan completo\n- [x] Tasks mapeadas\n- [x] Dependencias validadas\n- [x] Rastreabilidade User Stories -> Tasks preenchida\n- [x] Pronto para execucao paralela\n\n---\n---\n\n## TASKS INDIVIDUAIS\n\n---\n\n# T1 -- Criar migracao para adicionar coluna telefone\n\n## 1. Identificacao\n- **ID**: T1\n- **Nome da Task**: Criar migracao para adicionar coluna telefone\n- **Responsavel**: N/A\n- **Status**: A Fazer\n- **Fase**: 1 -- Preparacao / Banco de Dados\n- **Dependencias**: Nenhuma\n- **User Stories Relacionadas**: US-10\n\n---\n\n## 2. Objetivo da Task\nCriar arquivo de migracao SQL que adiciona a coluna `telefone` (TEXT, opcional) a tabela `usuarios`.\n\n---\n\n## 3. Descricao Detalhada\nCriar um novo arquivo de migracao seguindo a convencao de nomenclatura do projeto:\n\n1. **Arquivo**: `internal/db/migrations/002_add_telefone_usuarios.sql`\n2. **Conteudo**: Instrucao `ALTER TABLE usuarios ADD COLUMN telefone TEXT;`\n3. A coluna e opcional (sem `NOT NULL`), pois o telefone nao e obrigatorio\n4. NAO editar a migracao existente `001_create_usuarios.sql` (regra de imutabilidade de migracoes)\n5. A migracao sera executada automaticamente no boot da aplicacao via golang-migrate\n\n**Observacao**: O projeto usa apenas migracoes `.sql` (sem separacao up/down em arquivos distintos com base no que existe). Seguir o mesmo padrao da migracao `001_create_usuarios.sql`.\n\n---\n\n## 4. Aceite Tecnico (criterios objetivos)\nA task estara concluida quando:\n- [ ] Arquivo `002_add_telefone_usuarios.sql` criado em `internal/db/migrations/`\n- [ ] SQL contem `ALTER TABLE usuarios ADD COLUMN telefone TEXT;`\n- [ ] Coluna e opcional (sem NOT NULL)\n- [ ] Migracao 001 NAO foi editada\n- [ ] Nenhuma quebra nos fluxos existentes\n\n---\n\n## 5. Arquivos Impactados\n\n### 5.1 Arquivos a Criar\n| Arquivo | Descricao |\n|---------|-----------|\n| `internal/db/migrations/002_add_telefone_usuarios.sql` | Migracao para adicionar coluna telefone |\n\n### 5.2 Arquivos a Modificar\n| Arquivo | Modificacao |\n|---------|------------|\n| Nenhum | N/A |\n\n### 5.3 Arquivos de Referencia\n| Arquivo | Motivo da Consulta |\n|---------|-------------------|\n| `internal/db/migrations/001_create_usuarios.sql` | Referencia de padrao de migracao existente |\n\n---\n\n## 6. Testes\n\n### 6.1 Testes Unitarios\nN/A -- migracao SQL nao possui testes unitarios isolados.\n\n### 6.2 Testes de Integracao\n- [ ] Teste: Verificar que a migracao aplica corretamente e a coluna `telefone` existe na tabela `usuarios` apos execucao\n- [ ] Teste: Verificar que registros existentes na tabela `usuarios` continuam acessiveis apos a migracao (coluna `telefone` com valor NULL)\n\n### 6.3 Testes E2E\nN/A -- validacao sera feita indiretamente pelos testes das camadas superiores.\n\n### 6.4 Cenarios de Erro\n- [ ] Cenario: Migracao re-executada nao causa erro (idempotencia \u00e2\u20ac\u201d verificar comportamento do golang-migrate)\n\n---\n\n## 7. Notas / Observacoes\n- SQLite suporta `ALTER TABLE ADD COLUMN` normalmente\n- A coluna sera NULL para usuarios existentes, o que e o comportamento desejado (campo opcional)\n- O projeto usa um unico arquivo `.sql` por migracao (nao separa up/down em arquivos distintos), conforme padrao observado em `001_create_usuarios.sql`\n\n---\n\n## 8. Checklist Final\n- [ ] Implementada conforme SPEC\n- [ ] Testes unitarios criados/atualizados\n- [ ] Testes de integracao criados/atualizados\n- [ ] Aceite tecnico atendido\n- [ ] Revisada\n- [ ] Integrada a branch principal\n\n---\n---\n\n# T2 -- Atualizar queries SQLC para incluir telefone\n\n## 1. Identificacao\n- **ID**: T2\n- **Nome da Task**: Atualizar queries SQLC para incluir telefone\n- **Responsavel**: N/A\n- **Status**: A Fazer\n- **Fase**: 1 -- Preparacao / Banco de Dados\n- **Dependencias**: T1\n- **User Stories Relacionadas**: US-10\n\n---\n\n## 2. Objetivo da Task\nAtualizar as queries SQLC existentes para incluir a coluna `telefone` e criar a nova query `UpdateUser`. Apos editar, executar `make generate` para regenerar o codigo Go.\n\n---\n\n## 3. Descricao Detalhada\n\n### 3.1 Atualizar query `CreateUser`\nNo arquivo `internal/db/sqlc/queries/user.sql`, modificar a query `CreateUser` para incluir a coluna `telefone`:\n\n```sql\n-- name: CreateUser :one\nINSERT INTO usuarios (id, nome, email, senha_hash, telefone, data_criacao, data_atualizacao)\nVALUES (?, ?, ?, ?, ?, ?, ?)\nRETURNING *;\n```\n\n### 3.2 Criar query `UpdateUser`\nAdicionar nova query `UpdateUser` ao arquivo `internal/db/sqlc/queries/user.sql`:\n\n```sql\n-- name: UpdateUser :one\nUPDATE usuarios SET nome = ?, email = ?, telefone = ?, data_atualizacao = ?\nWHERE id = ?\nRETURNING *;\n```\n\n### 3.3 Query `GetUserByID`\nA query `GetUserByID` usa `SELECT *`, portanto ja retornara automaticamente a coluna `telefone`. NAO precisa ser alterada.\n\n### 3.4 Regenerar codigo\nExecutar `make generate` (ou `sqlc generate` dentro de `internal/db/sqlc/`) para regenerar os arquivos Go em `internal/db/sqlc/`.\n\n---\n\n## 4. Aceite Tecnico (criterios objetivos)\nA task estara concluida quando:\n- [ ] Query `CreateUser` inclui coluna `telefone`\n- [ ] Query `UpdateUser` criada com colunas `nome`, `email`, `telefone`, `data_atualizacao` e filtro por `id`\n- [ ] Query `GetUserByID` mantida inalterada (ja usa SELECT *)\n- [ ] `make generate` executado com sucesso\n- [ ] Codigo gerado em `internal/db/sqlc/` reflete as novas queries\n- [ ] Struct `CreateUserParams` gerada inclui campo `Telefone`\n- [ ] Struct `UpdateUserParams` gerada existe com campos corretos\n- [ ] Struct `Usuario` gerada inclui campo `Telefone`\n\n---\n\n## 5. Arquivos Impactados\n\n### 5.1 Arquivos a Criar\n| Arquivo | Descricao |\n|---------|-----------|\n| Nenhum | N/A |\n\n### 5.2 Arquivos a Modificar\n| Arquivo | Modificacao |\n|---------|------------|\n| `internal/db/sqlc/queries/user.sql` | Adicionar coluna `telefone` ao CreateUser e criar query UpdateUser |\n\n### 5.3 Arquivos de Referencia\n| Arquivo | Motivo da Consulta |\n|---------|-------------------|\n| `internal/db/sqlc/sqlc.yaml` | Configuracao do SQLC para verificar engine e paths |\n| `internal/db/migrations/002_add_telefone_usuarios.sql` | Schema atualizado com coluna telefone |\n\n---\n\n## 6. Testes\n\n### 6.1 Testes Unitarios\nN/A -- queries SQLC sao validadas na geracao e testadas via integracao.\n\n### 6.2 Testes de Integracao\n- [ ] Teste: Query `CreateUser` com campo `telefone` preenchido insere registro corretamente e retorna todos os campos incluindo `telefone`\n- [ ] Teste: Query `CreateUser` com campo `telefone` vazio (NULL) insere registro corretamente\n- [ ] Teste: Query `UpdateUser` atualiza `nome`, `email`, `telefone` e `data_atualizacao` corretamente\n- [ ] Teste: Query `UpdateUser` com `telefone` NULL atualiza corretamente\n- [ ] Teste: Query `GetUserByID` retorna campo `telefone` corretamente\n\n### 6.3 Testes E2E\nN/A -- coberto pelos testes de integracao e camadas superiores.\n\n### 6.4 Cenarios de Erro\n- [ ] Cenario: `UpdateUser` com ID inexistente retorna erro (sql.ErrNoRows)\n\n---\n\n## 7. Notas / Observacoes\n- NAO editar arquivos em `internal/db/sqlc/` diretamente (sao gerados)\n- Apenas editar `internal/db/sqlc/queries/user.sql`\n- Apos editar, obrigatoriamente rodar `make generate`\n- O campo `telefone` no SQLite sera do tipo `sql.NullString` no codigo gerado (por ser nullable)\n\n---\n\n## 8. Checklist Final\n- [ ] Implementada conforme SPEC\n- [ ] Testes unitarios criados/atualizados\n- [ ] Testes de integracao criados/atualizados\n- [ ] Aceite tecnico atendido\n- [ ] Revisada\n- [ ] Integrada a branch principal\n\n---\n---\n\n# T3 -- Atualizar repository com campo Phone e metodo Update\n\n## 1. Identificacao\n- **ID**: T3\n- **Nome da Task**: Atualizar repository com campo Phone e metodo Update\n- **Responsavel**: N/A\n- **Status**: A Fazer\n- **Fase**: 2 -- Implementacao Backend\n- **Dependencias**: T2\n- **User Stories Relacionadas**: US-10\n\n---\n\n## 2. Objetivo da Task\nAdicionar campo `Phone` ao modelo `User` do repository, atualizar o mapeamento SQLC, adicionar metodo `Update` a interface `UserRepository` e implementa-lo.\n\n---\n\n## 3. Descricao Detalhada\n\n### 3.1 Atualizar struct `User`\nEm `internal/repository/user_repository.go`, adicionar campo `Phone` a struct `User`:\n\n```go\ntype User struct {\n ID string\n Name string\n Email string\n PasswordHash string\n Phone string // mapeado de 'telefone' no banco\n CreatedAt time.Time\n UpdatedAt time.Time\n}\n```\n\n### 3.2 Atualizar interface `UserRepository`\nAdicionar metodo `Update` a interface:\n\n```go\ntype UserRepository interface {\n Create(ctx context.Context, user *User) (*User, error)\n GetByID(ctx context.Context, id string) (*User, error)\n Update(ctx context.Context, user *User) (*User, error)\n}\n```\n\n### 3.3 Atualizar metodo `Create`\nIncluir campo `Telefone` nos params do SQLC:\n\n```go\nparams := sqlc.CreateUserParams{\n // ... campos existentes ...\n Telefone: sql.NullString{String: user.Phone, Valid: user.Phone != \"\"},\n}\n```\n\n### 3.4 Implementar metodo `Update`\nCriar metodo `Update` na struct `sqlcUserRepository`:\n\n```go\nfunc (r *sqlcUserRepository) Update(ctx context.Context, user *User) (*User, error) {\n params := sqlc.UpdateUserParams{\n Nome: user.Name,\n Email: user.Email,\n Telefone: sql.NullString{String: user.Phone, Valid: user.Phone != \"\"},\n DataAtualizacao: user.UpdatedAt,\n ID: user.ID,\n }\n row, err := r.queries.UpdateUser(ctx, params)\n if err != nil {\n if err == sql.ErrNoRows {\n return nil, fmt.Errorf(\"repository: usuario com id %q nao encontrado\", user.ID)\n }\n return nil, fmt.Errorf(\"repository: falha ao atualizar usuario: %w\", err)\n }\n return toUser(row), nil\n}\n```\n\n### 3.5 Atualizar funcao `toUser`\nIncluir mapeamento do campo `Telefone`:\n\n```go\nfunc toUser(u sqlc.Usuario) *User {\n return &User{\n // ... campos existentes ...\n Phone: u.Telefone.String, // NullString -> string\n }\n}\n```\n\n---\n\n## 4. Aceite Tecnico (criterios objetivos)\nA task estara concluida quando:\n- [ ] Struct `User` possui campo `Phone string`\n- [ ] Interface `UserRepository` possui metodo `Update(ctx, *User) (*User, error)`\n- [ ] Metodo `Create` envia campo `Telefone` ao SQLC\n- [ ] Metodo `Update` implementado com mapeamento correto\n- [ ] Funcao `toUser` mapeia `Telefone` -> `Phone`\n- [ ] Tratamento de `sql.NullString` para campo nullable\n- [ ] Erros encapsulados com mensagens em portugues\n- [ ] Nenhuma quebra nos fluxos existentes\n\n---\n\n## 5. Arquivos Impactados\n\n### 5.1 Arquivos a Criar\n| Arquivo | Descricao |\n|---------|-----------|\n| Nenhum | N/A |\n\n### 5.2 Arquivos a Modificar\n| Arquivo | Modificacao |\n|---------|------------|\n| `internal/repository/user_repository.go` | Adicionar campo Phone ao User, metodo Update a interface e implementacao, atualizar Create e toUser |\n\n### 5.3 Arquivos de Referencia\n| Arquivo | Motivo da Consulta |\n|---------|-------------------|\n| `internal/db/sqlc/` (codigo gerado) | Verificar structs e metodos gerados pelo SQLC |\n\n---\n\n## 6. Testes\n\n### 6.1 Testes Unitarios\nN/A -- repository e testado via integracao (depende do banco).\n\n### 6.2 Testes de Integracao\n- [ ] Teste: `Create` com campo `Phone` preenchido persiste e retorna corretamente\n- [ ] Teste: `Create` com campo `Phone` vazio persiste com `telefone` NULL no banco\n- [ ] Teste: `Update` com campo `Phone` preenchido atualiza e retorna corretamente\n- [ ] Teste: `Update` com campo `Phone` vazio (limpar telefone) atualiza corretamente\n- [ ] Teste: `GetByID` retorna campo `Phone` corretamente\n- [ ] Teste: `Update` com ID inexistente retorna erro \"usuario nao encontrado\"\n\n### 6.3 Testes E2E\nN/A -- coberto pelos testes de integracao e testes do service.\n\n### 6.4 Cenarios de Erro\n- [ ] Cenario: `Update` com ID inexistente retorna erro adequado\n- [ ] Cenario: `Update` com email duplicado retorna erro de constraint\n\n---\n\n## 7. Notas / Observacoes\n- O campo `telefone` no banco e nullable, portanto usar `sql.NullString` nos params SQLC\n- Na conversao de volta (`toUser`), converter `NullString.String` para string simples (vazio se NULL)\n- O import de `database/sql` ja existe no arquivo\n\n---\n\n## 8. Checklist Final\n- [ ] Implementada conforme SPEC\n- [ ] Testes unitarios criados/atualizados\n- [ ] Testes de integracao criados/atualizados\n- [ ] Aceite tecnico atendido\n- [ ] Revisada\n- [ ] Integrada a branch principal\n\n---\n---\n\n# T4 -- Atualizar service com campo Phone, validacao e UpdateUser\n\n## 1. Identificacao\n- **ID**: T4\n- **Nome da Task**: Atualizar service com campo Phone, validacao e UpdateUser\n- **Responsavel**: N/A\n- **Status**: A Fazer\n- **Fase**: 2 -- Implementacao Backend\n- **Dependencias**: T3\n- **User Stories Relacionadas**: US-10\n\n---\n\n## 2. Objetivo da Task\nAdicionar campo `Phone` ao `CreateUserInput`, criar `UpdateUserInput`, adicionar metodo `UpdateUser` a interface `UserService`, implementar validacao de telefone (10-15 caracteres quando informado) e implementar o metodo `UpdateUser`.\n\n---\n\n## 3. Descricao Detalhada\n\n### 3.1 Atualizar `CreateUserInput`\nAdicionar campo `Phone` ao input de criacao:\n\n```go\ntype CreateUserInput struct {\n Name string\n Email string\n Password string\n Phone string // opcional\n}\n```\n\n### 3.2 Criar `UpdateUserInput`\nCriar nova struct de input para atualizacao:\n\n```go\ntype UpdateUserInput struct {\n ID string\n Name string\n Email string\n Phone string // opcional\n}\n```\n\n### 3.3 Criar variavel de erro para telefone\n```go\nvar ErrInvalidPhone = errors.New(\"telefone deve ter entre 10 e 15 caracteres\")\n```\n\n### 3.4 Atualizar interface `UserService`\nAdicionar metodo `UpdateUser`:\n\n```go\ntype UserService interface {\n CreateUser(ctx context.Context, input CreateUserInput) (*repository.User, error)\n GetUser(ctx context.Context, id string) (*repository.User, error)\n UpdateUser(ctx context.Context, input UpdateUserInput) (*repository.User, error)\n}\n```\n\n### 3.5 Atualizar metodo `CreateUser`\nAdicionar validacao de telefone e passar campo Phone ao repository:\n\n```go\nfunc (s *userServiceImpl) CreateUser(ctx context.Context, input CreateUserInput) (*repository.User, error) {\n // ... validacoes existentes ...\n\n // Validacao de telefone (opcional)\n if input.Phone != \"\" {\n if len(input.Phone) < 10 || len(input.Phone) > 15 {\n return nil, ErrInvalidPhone\n }\n }\n\n // ... hash de senha ...\n\n user := &repository.User{\n // ... campos existentes ...\n Phone: input.Phone,\n }\n return s.repo.Create(ctx, user)\n}\n```\n\n### 3.6 Implementar metodo `UpdateUser`\n```go\nfunc (s *userServiceImpl) UpdateUser(ctx context.Context, input UpdateUserInput) (*repository.User, error) {\n if input.ID == \"\" {\n return nil, errors.New(\"id e obrigatorio\")\n }\n if input.Name == \"\" {\n return nil, errors.New(\"nome e obrigatorio\")\n }\n if input.Email == \"\" {\n return nil, errors.New(\"email e obrigatorio\")\n }\n if input.Phone != \"\" {\n if len(input.Phone) < 10 || len(input.Phone) > 15 {\n return nil, ErrInvalidPhone\n }\n }\n\n now := time.Now()\n user := &repository.User{\n ID: input.ID,\n Name: input.Name,\n Email: input.Email,\n Phone: input.Phone,\n UpdatedAt: now,\n }\n return s.repo.Update(ctx, user)\n}\n```\n\n---\n\n## 4. Aceite Tecnico (criterios objetivos)\nA task estara concluida quando:\n- [ ] `CreateUserInput` possui campo `Phone`\n- [ ] `UpdateUserInput` criado com campos `ID`, `Name`, `Email`, `Phone`\n- [ ] Interface `UserService` possui metodo `UpdateUser`\n- [ ] Validacao de telefone: se informado, entre 10 e 15 caracteres\n- [ ] Validacao de telefone: se vazio, aceito sem erro (campo opcional)\n- [ ] `ErrInvalidPhone` definido como variavel de erro exportada\n- [ ] `CreateUser` passa campo `Phone` ao repository\n- [ ] `UpdateUser` implementado com validacoes de nome, email, id e phone\n- [ ] Erros com mensagens em portugues\n- [ ] Nenhuma quebra nos fluxos existentes\n\n---\n\n## 5. Arquivos Impactados\n\n### 5.1 Arquivos a Criar\n| Arquivo | Descricao |\n|---------|-----------|\n| Nenhum | N/A |\n\n### 5.2 Arquivos a Modificar\n| Arquivo | Modificacao |\n|---------|------------|\n| `internal/service/user_service.go` | Adicionar Phone ao CreateUserInput, criar UpdateUserInput, UpdateUser, validacao de telefone |\n\n### 5.3 Arquivos de Referencia\n| Arquivo | Motivo da Consulta |\n|---------|-------------------|\n| `internal/repository/user_repository.go` | Interface UserRepository e struct User atualizadas |\n\n---\n\n## 6. Testes\n\n### 6.1 Testes Unitarios\n- [ ] Teste: `CreateUser` com phone valido (10 caracteres) cria usuario com sucesso\n- [ ] Teste: `CreateUser` com phone valido (15 caracteres) cria usuario com sucesso\n- [ ] Teste: `CreateUser` com phone vazio cria usuario com sucesso (campo opcional)\n- [ ] Teste: `CreateUser` com phone invalido (9 caracteres) retorna `ErrInvalidPhone`\n- [ ] Teste: `CreateUser` com phone invalido (16 caracteres) retorna `ErrInvalidPhone`\n- [ ] Teste: `UpdateUser` com dados validos atualiza usuario com sucesso\n- [ ] Teste: `UpdateUser` com phone valido atualiza com sucesso\n- [ ] Teste: `UpdateUser` com phone vazio atualiza com sucesso\n- [ ] Teste: `UpdateUser` com phone invalido retorna `ErrInvalidPhone`\n- [ ] Teste: `UpdateUser` com ID vazio retorna erro\n- [ ] Teste: `UpdateUser` com nome vazio retorna erro\n- [ ] Teste: `UpdateUser` com email vazio retorna erro\n- [ ] Teste: `UpdateUser` com erro do repository propaga erro\n\n### 6.2 Testes de Integracao\nN/A -- service e testado com mocks do repository (unitarios).\n\n### 6.3 Testes E2E\nN/A -- coberto pelos testes do handler.\n\n### 6.4 Cenarios de Erro\n- [ ] Cenario: Validacao de telefone com caracteres no limite inferior (10) e superior (15) -- boundary test\n- [ ] Cenario: Repository retorna erro na atualizacao (ex: usuario nao encontrado)\n\n### Testes existentes a modificar\n| Arquivo | Modificacao |\n|---------|------------|\n| `internal/service/user_service_test.go` | Atualizar mock `MockUserRepository` para incluir metodo `Update`. Atualizar testes de `CreateUser` para incluir campo `Phone` no `CreateUserInput`. Adicionar novos testes para `UpdateUser` e validacao de telefone. |\n\n---\n\n## 7. Notas / Observacoes\n- O mock `MockUserRepository` no arquivo de testes precisa ser atualizado para incluir o metodo `Update`\n- Os testes existentes de `CreateUser` continuam validos, pois `Phone` e opcional (zero value \"\" e aceito)\n- Padrao table-driven deve ser usado para testes de validacao de telefone (conforme padrao existente)\n\n---\n\n## 8. Checklist Final\n- [ ] Implementada conforme SPEC\n- [ ] Testes unitarios criados/atualizados\n- [ ] Testes de integracao criados/atualizados\n- [ ] Aceite tecnico atendido\n- [ ] Revisada\n- [ ] Integrada a branch principal\n\n---\n---\n\n# T5 -- Atualizar proto com campo phone e endpoint UpdateUser\n\n## 1. Identificacao\n- **ID**: T5\n- **Nome da Task**: Atualizar proto com campo phone e endpoint UpdateUser\n- **Responsavel**: N/A\n- **Status**: A Fazer\n- **Fase**: 3 -- Interface gRPC\n- **Dependencias**: Nenhuma\n- **User Stories Relacionadas**: US-10\n\n---\n\n## 2. Objetivo da Task\nAtualizar o arquivo `api/proto/v1/user.proto` para adicionar campo `phone` nas mensagens existentes, criar mensagens `UpdateUserRequest`/`UpdateUserResponse` e adicionar RPC `UpdateUser` ao service. Executar `make generate` para regenerar codigo.\n\n---\n\n## 3. Descricao Detalhada\n\n### 3.1 Adicionar campo `phone` ao `CreateUserRequest`\n```proto\nmessage CreateUserRequest {\n string name = 1;\n string email = 2;\n string password = 3;\n string phone = 4; // opcional\n}\n```\n\n### 3.2 Adicionar campo `phone` ao `CreateUserResponse`\n```proto\nmessage CreateUserResponse {\n string id = 1;\n string name = 2;\n string email = 3;\n string created_at = 4;\n string phone = 5;\n}\n```\n\n### 3.3 Adicionar campo `phone` ao `GetUserResponse`\n```proto\nmessage GetUserResponse {\n string id = 1;\n string name = 2;\n string email = 3;\n string created_at = 4;\n string updated_at = 5;\n string phone = 6;\n}\n```\n\n### 3.4 Criar mensagens `UpdateUserRequest` e `UpdateUserResponse`\n```proto\nmessage UpdateUserRequest {\n string id = 1;\n string name = 2;\n string email = 3;\n string phone = 4;\n}\n\nmessage UpdateUserResponse {\n string id = 1;\n string name = 2;\n string email = 3;\n string phone = 4;\n string created_at = 5;\n string updated_at = 6;\n}\n```\n\n### 3.5 Adicionar RPC `UpdateUser` ao service\n```proto\nservice UserService {\n rpc CreateUser(CreateUserRequest) returns (CreateUserResponse);\n rpc GetUser(GetUserRequest) returns (GetUserResponse);\n rpc UpdateUser(UpdateUserRequest) returns (UpdateUserResponse);\n}\n```\n\n### 3.6 Regenerar codigo\nExecutar `make generate` (buf generate) para regenerar `gen/proto/v1/`.\n\n---\n\n## 4. Aceite Tecnico (criterios objetivos)\nA task estara concluida quando:\n- [ ] Campo `phone` adicionado a `CreateUserRequest` (field number 4)\n- [ ] Campo `phone` adicionado a `CreateUserResponse` (field number 5)\n- [ ] Campo `phone` adicionado a `GetUserResponse` (field number 6)\n- [ ] Mensagens `UpdateUserRequest` e `UpdateUserResponse` criadas\n- [ ] RPC `UpdateUser` adicionado ao `UserService`\n- [ ] `make generate` executado com sucesso\n- [ ] Codigo gerado em `gen/proto/v1/` reflete as novas mensagens e RPC\n- [ ] Numeracao de campos nao reutiliza numeros existentes\n\n---\n\n## 5. Arquivos Impactados\n\n### 5.1 Arquivos a Criar\n| Arquivo | Descricao |\n|---------|-----------|\n| Nenhum | N/A |\n\n### 5.2 Arquivos a Modificar\n| Arquivo | Modificacao |\n|---------|------------|\n| `api/proto/v1/user.proto` | Adicionar campo phone em messages existentes, criar UpdateUser request/response, adicionar RPC UpdateUser |\n\n### 5.3 Arquivos de Referencia\n| Arquivo | Motivo da Consulta |\n|---------|-------------------|\n| `buf.yaml` | Verificar regras de lint |\n| `buf.gen.yaml` | Verificar plugins de geracao |\n\n---\n\n## 6. Testes\n\n### 6.1 Testes Unitarios\nN/A -- arquivo proto nao possui testes unitarios. Validacao e feita pelo buf lint.\n\n### 6.2 Testes de Integracao\nN/A -- coberto pelos testes do handler.\n\n### 6.3 Testes E2E\nN/A -- coberto pelos testes do handler.\n\n### 6.4 Cenarios de Erro\n- [ ] Cenario: `buf lint` passa sem erros apos modificacoes\n- [ ] Cenario: `make generate` compila sem erros\n\n---\n\n## 7. Notas / Observacoes\n- Esta task pode rodar em paralelo com T1 (sao independentes)\n- NAO editar arquivos em `gen/` diretamente\n- Apenas editar `api/proto/v1/user.proto`\n- Apos editar, obrigatoriamente rodar `make generate`\n- Field numbers novos devem seguir sequencia existente sem reutilizar numeros\n\n---\n\n## 8. Checklist Final\n- [ ] Implementada conforme SPEC\n- [ ] Testes unitarios criados/atualizados\n- [ ] Testes de integracao criados/atualizados\n- [ ] Aceite tecnico atendido\n- [ ] Revisada\n- [ ] Integrada a branch principal\n\n---\n---\n\n# T6 -- Atualizar handler com campo phone e endpoint UpdateUser\n\n## 1. Identificacao\n- **ID**: T6\n- **Nome da Task**: Atualizar handler com campo phone e endpoint UpdateUser\n- **Responsavel**: N/A\n- **Status**: A Fazer\n- **Fase**: 3 -- Interface gRPC\n- **Dependencias**: T4, T5\n- **User Stories Relacionadas**: US-10\n\n---\n\n## 2. Objetivo da Task\nAtualizar o handler gRPC `CreateUser` para incluir campo `phone`, implementar handler `UpdateUser`, atualizar `GetUser` para retornar `phone`, e atualizar o mapeamento de erros para incluir validacao de telefone. Registrar o endpoint `UpdateUser` no servidor gRPC.\n\n---\n\n## 3. Descricao Detalhada\n\n### 3.1 Atualizar handler `CreateUser`\nEm `internal/handler/grpc/user_handler.go`, incluir campo `Phone` no input e na response:\n\n```go\nfunc (h *UserHandler) CreateUser(ctx context.Context, req *pb.CreateUserRequest) (*pb.CreateUserResponse, error) {\n input := service.CreateUserInput{\n Name: req.GetName(),\n Email: req.GetEmail(),\n Password: req.GetPassword(),\n Phone: req.GetPhone(),\n }\n user, err := h.userService.CreateUser(ctx, input)\n if err != nil {\n return nil, mapServiceError(err)\n }\n return &pb.CreateUserResponse{\n Id: user.ID,\n Name: user.Name,\n Email: user.Email,\n Phone: user.Phone,\n CreatedAt: user.CreatedAt.Format(time.RFC3339),\n }, nil\n}\n```\n\n### 3.2 Atualizar handler `GetUser`\nIncluir campo `Phone` na response:\n\n```go\nreturn &pb.GetUserResponse{\n // ... campos existentes ...\n Phone: user.Phone,\n}, nil\n```\n\n### 3.3 Implementar handler `UpdateUser`\n```go\nfunc (h *UserHandler) UpdateUser(ctx context.Context, req *pb.UpdateUserRequest) (*pb.UpdateUserResponse, error) {\n input := service.UpdateUserInput{\n ID: req.GetId(),\n Name: req.GetName(),\n Email: req.GetEmail(),\n Phone: req.GetPhone(),\n }\n user, err := h.userService.UpdateUser(ctx, input)\n if err != nil {\n return nil, mapServiceError(err)\n }\n return &pb.UpdateUserResponse{\n Id: user.ID,\n Name: user.Name,\n Email: user.Email,\n Phone: user.Phone,\n CreatedAt: user.CreatedAt.Format(time.RFC3339),\n UpdatedAt: user.UpdatedAt.Format(time.RFC3339),\n }, nil\n}\n```\n\n### 3.4 Atualizar `mapServiceError`\nAdicionar mapeamento para erro de telefone invalido (ja coberto pelo pattern \"obrigatorio\" existente, mas verificar se \"deve ter entre\" precisa de tratamento):\n\nO erro `ErrInvalidPhone` (\"telefone deve ter entre 10 e 15 caracteres\") deve ser mapeado para `codes.InvalidArgument`. Verificar se o pattern existente no `mapServiceError` cobre este caso. Se nao cobrir, adicionar verificacao.\n\n### 3.5 Configurar autenticacao\nSe `UpdateUser` deve ser protegido (requer autenticacao), NAO adicionar a `skip_methods` em `configs/config.yaml`. Se deve ser publico, adicionar. Conforme o SPEC_TECH, nao ha indicacao de que deva ser publico, entao sera protegido por padrao.\n\n---\n\n## 4. Aceite Tecnico (criterios objetivos)\nA task estara concluida quando:\n- [ ] Handler `CreateUser` passa campo `Phone` ao service e retorna na response\n- [ ] Handler `GetUser` retorna campo `Phone` na response\n- [ ] Handler `UpdateUser` implementado e funcional\n- [ ] Mapeamento de erros atualizado para erro de telefone invalido\n- [ ] Nenhuma quebra nos fluxos existentes\n- [ ] Handler compila corretamente contra o proto gerado\n\n---\n\n## 5. Arquivos Impactados\n\n### 5.1 Arquivos a Criar\n| Arquivo | Descricao |\n|---------|-----------|\n| Nenhum | N/A |\n\n### 5.2 Arquivos a Modificar\n| Arquivo | Modificacao |\n|---------|------------|\n| `internal/handler/grpc/user_handler.go` | Atualizar CreateUser, GetUser e criar UpdateUser com campo phone. Atualizar mapServiceError. |\n\n### 5.3 Arquivos de Referencia\n| Arquivo | Motivo da Consulta |\n|---------|-------------------|\n| `gen/proto/v1/` (codigo gerado) | Verificar interfaces e structs geradas pelo proto |\n| `internal/service/user_service.go` | Interface UserService atualizada |\n| `configs/config.yaml` | Verificar skip_methods para autenticacao |\n\n---\n\n## 6. Testes\n\n### 6.1 Testes Unitarios\n- [ ] Teste: `CreateUser` com phone valido retorna response com campo phone preenchido\n- [ ] Teste: `CreateUser` com phone vazio retorna response com phone vazio\n- [ ] Teste: `CreateUser` com phone invalido retorna `codes.InvalidArgument`\n- [ ] Teste: `GetUser` retorna campo phone na response\n- [ ] Teste: `UpdateUser` com dados validos retorna response correta\n- [ ] Teste: `UpdateUser` com phone valido retorna response com phone preenchido\n- [ ] Teste: `UpdateUser` com phone vazio retorna response com phone vazio\n- [ ] Teste: `UpdateUser` com phone invalido retorna `codes.InvalidArgument`\n- [ ] Teste: `UpdateUser` com ID vazio retorna `codes.InvalidArgument`\n- [ ] Teste: `UpdateUser` com usuario nao encontrado retorna `codes.NotFound`\n- [ ] Teste: `UpdateUser` com erro interno retorna `codes.Internal`\n- [ ] Teste: `UpdateUser` mapeamento de response (todos os campos corretos)\n\n### 6.2 Testes de Integracao\nN/A -- handler e testado com mocks do service (unitarios).\n\n### 6.3 Testes E2E\nN/A -- para esta feature simples, os testes unitarios do handler cobrem o fluxo completo.\n\n### 6.4 Cenarios de Erro\n- [ ] Cenario: Erro de validacao de telefone mapeado corretamente para `codes.InvalidArgument`\n- [ ] Cenario: Erro \"nao encontrado\" do service mapeado para `codes.NotFound`\n- [ ] Cenario: Erro generico do service mapeado para `codes.Internal`\n\n### Testes existentes a modificar\n| Arquivo | Modificacao |\n|---------|------------|\n| `internal/handler/grpc/user_handler_test.go` | Atualizar mock `MockUserService` para incluir metodo `UpdateUser`. Atualizar testes de `CreateUser` para validar campo `Phone` na response. Atualizar testes de `GetUser` para validar campo `Phone` na response. Adicionar novos testes para handler `UpdateUser`. |\n\n---\n\n## 7. Notas / Observacoes\n- O mock `MockUserService` no arquivo de testes precisa ser atualizado para incluir o metodo `UpdateUser`\n- Os testes existentes de `CreateUser` e `GetUser` precisam ser atualizados para incluir assertions do campo `Phone`\n- O handler `UserHandler` ja implementa `UnimplementedUserServiceServer`, entao `UpdateUser` sera automaticamente parte da interface apos regenerar o proto\n- Verificar se `mapServiceError` cobre o novo erro de telefone invalido\n\n---\n\n## 8. Checklist Final\n- [ ] Implementada conforme SPEC\n- [ ] Testes unitarios criados/atualizados\n- [ ] Testes de integracao criados/atualizados\n- [ ] Aceite tecnico atendido\n- [ ] Revisada\n- [ ] Integrada a branch principal\n\n---\n---\n\n## Resumo Final\n\n### Arquivos que seriam salvos (simulacao -- nao salvos em docs/ conforme instrucoes do eval):\n```\ndocs/telefone-usuario/v1/task_plan.md\ndocs/telefone-usuario/v1/tasks/T1.md\ndocs/telefone-usuario/v1/tasks/T2.md\ndocs/telefone-usuario/v1/tasks/T3.md\ndocs/telefone-usuario/v1/tasks/T4.md\ndocs/telefone-usuario/v1/tasks/T5.md\ndocs/telefone-usuario/v1/tasks/T6.md\n```\n\n### Ordem de execucao recomendada:\n```\nParalelo: T1 + T5\n |\n v\n T2 (apos T1)\n |\n v\n T3 (apos T2)\n |\n v\n T4 (apos T3)\n |\n v\n T6 (apos T4 + T5)\n```\n\nTask Plan aprovado para execucao? (sim/nao)\n"}], "grading": {"eval_id": 2, "eval_name": "spec-tech-simples-alteracao-pontual", "configuration": "with_skill", "expectations": [{"text": "Gera um numero proporcional de tasks (entre 3 e 6) \u00e2\u20ac\u201d nao over-engineers com tasks demais para uma alteracao simples", "passed": true, "evidence": "Gerou exatamente 6 tasks (T1-T6), dentro da faixa esperada de 3-6. Proporcional a complexidade da feature."}, {"text": "Respeita a ordem de dependencias das camadas (migracao > SQLC > repository > service > handler > proto)", "passed": true, "evidence": "Dependencias: T1(migracao)->T2(SQLC)->T3(repo)->T4(service)->T6(handler), T5(proto)->T6(handler). Proto em paralelo com migracao. Ordem correta."}, {"text": "Mapeia a US-10 na tabela de rastreabilidade do task_plan", "passed": true, "evidence": "Secao 5 do task_plan contem tabela de rastreabilidade mapeando US-10 para todas as 6 tasks com definicoes tecnicas correspondentes."}, {"text": "Cada task segue o template completo (secoes 1-8) ou marca secoes N/A com justificativa", "passed": true, "evidence": "Todas as 6 tasks (T1-T6) seguem o template com secoes 1-8 preenchidas. Secoes N/A possuem justificativa (ex: T1 secao 6.1: 'N/A -- migracao SQL nao possui testes unitarios isolados')."}, {"text": "Salva task_plan.md como documento de REFERENCIA (sem corpo detalhado de tasks dentro dele)", "passed": true, "evidence": "O task_plan contem apenas identificacao, macro-fases, tabela de tasks com IDs/links, rastreabilidade, dependencias e criterios. Tasks detalhadas estao separadas (T1-T6). Embora tudo esteja em um arquivo de output por ser eval, a estrutura esta correta."}]}}, {"id": "eval-2-spec-simples-without_skill", "prompt": "Preciso de um TASK PLAN para esta feature simples. O SPEC_TECH aprovado esta abaixo:\n\n---\n# SPEC_TECH \u00e2\u20ac\u201d Adicionar Telefone ao Usuario v1\n\n## 1. Identificacao\n| Campo | Valor |\n|-------|-------|\n| **Feature/Projeto** | Adicionar Telefone ao Modulo de Usuario |\n| **Versao** | v1 |\n| **Data** | 2026-03-07 |\n| **PRD** | docs/feature-telefone-usuario/v1/prd.md |\n\n## 2. Resumo Tecnico\nAdicionar campo `telefone` (opcional) a tabela `usuarios` e expor via gRPC nos endpoints CreateUser e UpdateUser.\n\n## 3. Definicoes Tecnicas\n\n### 3.1 Banco de Dados\n- Nova migracao: `ALTER TABLE usuarios ADD COLUMN telefone TEXT;`\n\n### 3.2 SQLC\n- Atualizar queries CreateUser e UpdateUser para incluir coluna `telefone`\n\n### 3.3 Repository\n- Adicionar campo `Phone` ao modelo User\n- Mapear `telefone` (banco) para `Phone` (Go)\n\n### 3.4 Service\n- Aceitar campo opcional phone em CreateUser e UpdateUser\n- Validacao: se informado, deve ter entre 10 e 15 caracteres\n\n### 3.5 Handler gRPC\n- Adicionar campo `phone` nas mensagens proto CreateUserRequest, UpdateUserRequest e User\n\n### 3.6 Proto\n- Atualizar user.proto com campo `string phone = X;`\n\n## 4. User Stories Relacionadas\n- US-10: Como usuario, quero informar meu telefone para receber notificacoes por SMS\n---", "eval_id": 2, "outputs": [{"name": "response.md", "type": "text", "content": "# TASK PLAN \u00e2\u20ac\u201d Adicionar Telefone ao Usuario v1\n\n## 1. Identificacao\n\n| Campo | Valor |\n|-------|-------|\n| **Feature** | Adicionar Telefone ao Modulo de Usuario |\n| **Versao** | v1 |\n| **Data** | 2026-03-07 |\n| **SPEC_TECH** | docs/feature-telefone-usuario/v1/spec_tech.md |\n| **Branch base** | user-feature |\n\n## 2. Resumo\n\nAdicionar o campo opcional `telefone` na tabela `usuarios` e propagar a mudanca por todas as camadas: migracao SQL, queries SQLC, repository, service, proto e handler gRPC. Inclui validacao opcional (10-15 caracteres quando informado) e testes unitarios.\n\n## 3. Tarefas\n\n### TASK-1: Criar migracao SQL para adicionar coluna telefone\n\n**Camada:** Banco de Dados\n**Arquivo a criar:** `internal/db/migrations/002_add_telefone_usuarios.sql`\n**Descricao:**\nCriar nova migracao com `ALTER TABLE usuarios ADD COLUMN telefone TEXT;`. Conforme regra de imutabilidade, nao editar a migracao 001 existente.\n\n**Conteudo esperado:**\n```sql\nALTER TABLE usuarios ADD COLUMN telefone TEXT;\n```\n\n**Criterio de aceite:**\n- Arquivo criado em `internal/db/migrations/002_add_telefone_usuarios.sql`\n- Migracao 001 inalterada\n- Coluna `telefone` do tipo `TEXT` (nullable/opcional)\n\n**Dependencias:** Nenhuma\n\n---\n\n### TASK-2: Atualizar queries SQLC para incluir telefone\n\n**Camada:** SQLC Queries\n**Arquivo a editar:** `internal/db/sqlc/queries/user.sql`\n**Descricao:**\nAdicionar a coluna `telefone` na query `CreateUser` (INSERT e parametros) e criar uma nova query `UpdateUser` que permita atualizar nome e telefone.\n\n**Alteracoes esperadas:**\n\nQuery `CreateUser` atualizada:\n```sql\n-- name: CreateUser :one\nINSERT INTO usuarios (id, nome, email, senha_hash, telefone, data_criacao, data_atualizacao)\nVALUES (?, ?, ?, ?, ?, ?, ?)\nRETURNING *;\n```\n\nNova query `UpdateUser`:\n```sql\n-- name: UpdateUser :one\nUPDATE usuarios SET nome = ?, telefone = ?, data_atualizacao = ?\nWHERE id = ?\nRETURNING *;\n```\n\n**Criterio de aceite:**\n- Query `CreateUser` inclui coluna `telefone`\n- Nova query `UpdateUser` criada\n- Executar `make generate` (ou `make sqlc`) apos a edicao para regenerar codigo\n\n**Dependencias:** TASK-1\n\n---\n\n### TASK-3: Regenerar codigo SQLC\n\n**Camada:** Codigo Gerado\n**Descricao:**\nExecutar `make generate` para que o SQLC regenere os arquivos Go em `internal/db/sqlc/` com a nova coluna `telefone` e a nova query `UpdateUser`.\n\n**Comando:**\n```bash\nmake generate\n```\n\n**Criterio de aceite:**\n- Struct `Usuario` gerada inclui campo `Telefone`\n- Struct `CreateUserParams` inclui campo `Telefone`\n- Struct `UpdateUserParams` gerada\n- Funcao `UpdateUser` gerada em queries\n\n**Dependencias:** TASK-1, TASK-2\n\n---\n\n### TASK-4: Atualizar modelo e repository do usuario\n\n**Camada:** Repository\n**Arquivo a editar:** `internal/repository/user_repository.go`\n**Descricao:**\nAdicionar campo `Phone` ao modelo `User`. Atualizar o mapeamento na funcao `toUser` para converter `telefone` (banco) para `Phone` (Go). Atualizar `Create` para passar `telefone` nos parametros SQLC. Adicionar metodo `Update` na interface e implementacao.\n\n**Alteracoes esperadas:**\n\n1. Adicionar campo na struct:\n```go\ntype User struct {\n ID string\n Name string\n Email string\n PasswordHash string\n Phone string // novo campo\n CreatedAt time.Time\n UpdatedAt time.Time\n}\n```\n\n2. Atualizar interface:\n```go\ntype UserRepository interface {\n Create(ctx context.Context, user *User) (*User, error)\n GetByID(ctx context.Context, id string) (*User, error)\n Update(ctx context.Context, user *User) (*User, error) // novo metodo\n}\n```\n\n3. Atualizar `Create` para incluir `Telefone` nos params SQLC\n4. Implementar metodo `Update` usando `r.queries.UpdateUser`\n5. Atualizar funcao `toUser` para mapear `Telefone` -> `Phone`\n\n**Criterio de aceite:**\n- Campo `Phone` presente no modelo `User`\n- Mapeamento `Telefone` <-> `Phone` funcional em `toUser`\n- Metodo `Update` implementado\n- `Create` passa `telefone` para o SQLC\n\n**Dependencias:** TASK-3\n\n---\n\n### TASK-5: Atualizar service do usuario\n\n**Camada:** Service\n**Arquivo a editar:** `internal/service/user_service.go`\n**Descricao:**\nAdicionar campo `Phone` ao `CreateUserInput`. Criar struct `UpdateUserInput`. Adicionar metodo `UpdateUser` na interface e implementacao. Implementar validacao: se `phone` informado, deve ter entre 10 e 15 caracteres.\n\n**Alteracoes esperadas:**\n\n1. Atualizar input de criacao:\n```go\ntype CreateUserInput struct {\n Name string\n Email string\n Password string\n Phone string // novo, opcional\n}\n```\n\n2. Criar input de atualizacao:\n```go\ntype UpdateUserInput struct {\n ID string\n Name string\n Phone string // opcional\n}\n```\n\n3. Atualizar interface:\n```go\ntype UserService interface {\n CreateUser(ctx context.Context, input CreateUserInput) (*repository.User, error)\n GetUser(ctx context.Context, id string) (*repository.User, error)\n UpdateUser(ctx context.Context, input UpdateUserInput) (*repository.User, error) // novo\n}\n```\n\n4. Adicionar validacao de telefone (reutilizavel em Create e Update):\n```go\nfunc validatePhone(phone string) error {\n if phone != \"\" && (len(phone) < 10 || len(phone) > 15) {\n return errors.New(\"telefone deve ter entre 10 e 15 caracteres\")\n }\n return nil\n}\n```\n\n5. Chamar `validatePhone` em `CreateUser` e `UpdateUser`\n6. Passar `Phone` para `repository.User` em `CreateUser`\n7. Implementar `UpdateUser` com busca por ID + chamada ao repo\n\n**Criterio de aceite:**\n- Campo `Phone` aceito em `CreateUser`\n- Metodo `UpdateUser` implementado\n- Validacao de telefone (10-15 chars) funcional\n- Telefone vazio e aceito (campo opcional)\n\n**Dependencias:** TASK-4\n\n---\n\n### TASK-6: Atualizar definicao Proto\n\n**Camada:** Proto/gRPC\n**Arquivo a editar:** `api/proto/v1/user.proto`\n**Descricao:**\nAdicionar campo `string phone` nas mensagens `CreateUserRequest`, `CreateUserResponse`, `GetUserResponse`. Criar mensagens `UpdateUserRequest` e `UpdateUserResponse`. Adicionar RPC `UpdateUser` ao service.\n\n**Alteracoes esperadas:**\n\n1. Adicionar `string phone = 4;` em `CreateUserRequest`\n2. Adicionar `string phone = 4;` em `CreateUserResponse` (apos `email`, ajustar numeracao de `created_at` para 5)\n3. Adicionar `string phone = 4;` em `GetUserResponse` (apos `email`, ajustar numeracao: `phone=4`, `created_at=5`, `updated_at=6`)\n\n> **ATENCAO:** A numeracao dos campos existentes NAO pode ser alterada sem quebrar compatibilidade. Verificar a numeracao atual antes de definir os novos numeros. Dado que `CreateUserResponse` ja usa campos 1-4 e `GetUserResponse` usa 1-5, o campo `phone` deve usar o proximo numero disponivel em cada mensagem.\n\nNumeracao correta baseada no proto atual:\n- `CreateUserRequest`: `string phone = 4;` (campos existentes: 1=name, 2=email, 3=password)\n- `CreateUserResponse`: `string phone = 5;` (campos existentes: 1=id, 2=name, 3=email, 4=created_at)\n- `GetUserResponse`: `string phone = 6;` (campos existentes: 1=id, 2=name, 3=email, 4=created_at, 5=updated_at)\n\n4. Criar mensagens novas:\n```proto\nmessage UpdateUserRequest {\n string id = 1;\n string name = 2;\n string phone = 3;\n}\n\nmessage UpdateUserResponse {\n string id = 1;\n string name = 2;\n string email = 3;\n string phone = 4;\n string updated_at = 5;\n}\n```\n\n5. Adicionar ao service:\n```proto\nrpc UpdateUser(UpdateUserRequest) returns (UpdateUserResponse);\n```\n\n**Criterio de aceite:**\n- Campo `phone` adicionado nas mensagens existentes com numeracao correta\n- Mensagens `UpdateUserRequest`/`UpdateUserResponse` criadas\n- RPC `UpdateUser` registrado no service\n- Numeracao de campos existentes NAO alterada\n\n**Dependencias:** Nenhuma (pode ser feita em paralelo com TASK-1 a TASK-5)\n\n---\n\n### TASK-7: Regenerar codigo Proto\n\n**Camada:** Codigo Gerado\n**Descricao:**\nExecutar `make generate` (buf generate) para regenerar o codigo Go em `gen/proto/v1/`.\n\n**Comando:**\n```bash\nmake generate\n```\n\n**Criterio de aceite:**\n- Structs Go geradas incluem campo `Phone`\n- Interface `UserServiceServer` inclui metodo `UpdateUser`\n- Compilacao sem erros\n\n**Dependencias:** TASK-6\n\n---\n\n### TASK-8: Atualizar handler gRPC do usuario\n\n**Camada:** Handler\n**Arquivo a editar:** `internal/handler/grpc/user_handler.go`\n**Descricao:**\nAtualizar `CreateUser` para passar `Phone` do request para o service input e incluir `phone` na response. Implementar metodo `UpdateUser`. Atualizar `GetUser` para incluir `phone` na response.\n\n**Alteracoes esperadas:**\n\n1. Em `CreateUser`:\n - Adicionar `Phone: req.GetPhone()` ao `CreateUserInput`\n - Adicionar `Phone: user.Phone` na response\n\n2. Em `GetUser`:\n - Adicionar `Phone: user.Phone` na response\n\n3. Novo metodo `UpdateUser`:\n```go\nfunc (h *UserHandler) UpdateUser(ctx context.Context, req *pb.UpdateUserRequest) (*pb.UpdateUserResponse, error) {\n input := service.UpdateUserInput{\n ID: req.GetId(),\n Name: req.GetName(),\n Phone: req.GetPhone(),\n }\n user, err := h.userService.UpdateUser(ctx, input)\n if err != nil {\n return nil, mapServiceError(err)\n }\n return &pb.UpdateUserResponse{\n Id: user.ID,\n Name: user.Name,\n Email: user.Email,\n Phone: user.Phone,\n UpdatedAt: user.UpdatedAt.Format(time.RFC3339),\n }, nil\n}\n```\n\n**Criterio de aceite:**\n- `CreateUser` repassa e retorna `phone`\n- `GetUser` retorna `phone`\n- `UpdateUser` implementado e funcional\n- Erros mapeados corretamente\n\n**Dependencias:** TASK-5, TASK-7\n\n---\n\n### TASK-9: Configurar UpdateUser como rota protegida\n\n**Camada:** Configuracao\n**Arquivo a verificar:** `configs/config.yaml`\n**Descricao:**\nVerificar que `UpdateUser` NAO esta em `skip_methods` (ou seja, requer autenticacao). Apenas `CreateUser` deve ser publico. Se necessario, nenhuma alteracao e necessaria, apenas confirmar.\n\n**Criterio de aceite:**\n- `UpdateUser` exige autenticacao (Bearer token)\n- Nenhuma rota protegida acidentalmente exposta\n\n**Dependencias:** TASK-8\n\n---\n\n### TASK-10: Atualizar testes unitarios do service\n\n**Camada:** Testes\n**Arquivo a editar:** `internal/service/user_service_test.go`\n**Descricao:**\nAtualizar mock do repository para incluir metodo `Update`. Adicionar testes para:\n\n1. `TestCreateUser_WithPhone_Success` \u00e2\u20ac\u201d criacao com telefone valido\n2. `TestCreateUser_WithInvalidPhone_TooShort` \u00e2\u20ac\u201d telefone com menos de 10 chars\n3. `TestCreateUser_WithInvalidPhone_TooLong` \u00e2\u20ac\u201d telefone com mais de 15 chars\n4. `TestCreateUser_WithEmptyPhone_Success` \u00e2\u20ac\u201d telefone vazio (opcional)\n5. `TestUpdateUser_Success` \u00e2\u20ac\u201d atualizacao com dados validos\n6. `TestUpdateUser_WithPhone_Success` \u00e2\u20ac\u201d atualizacao com telefone valido\n7. `TestUpdateUser_InvalidPhone` \u00e2\u20ac\u201d telefone invalido na atualizacao\n8. `TestUpdateUser_NotFound` \u00e2\u20ac\u201d usuario nao encontrado\n\n**Criterio de aceite:**\n- Mock atualizado com metodo `Update`\n- Todos os cenarios de telefone cobertos (valido, vazio, curto, longo)\n- Testes de `UpdateUser` adicionados\n- Todos os testes passam: `CGO_ENABLED=1 go test ./internal/service/... -v`\n\n**Dependencias:** TASK-5\n\n---\n\n### TASK-11: Atualizar testes unitarios do handler\n\n**Camada:** Testes\n**Arquivo a editar:** `internal/handler/grpc/user_handler_test.go`\n**Descricao:**\nAtualizar mock do service para incluir metodo `UpdateUser`. Adicionar testes para:\n\n1. `TestUserHandler_CreateUser_WithPhone` \u00e2\u20ac\u201d verifica que phone e repassado e retornado\n2. `TestUserHandler_GetUser_WithPhone` \u00e2\u20ac\u201d verifica que phone e retornado\n3. `TestUserHandler_UpdateUser_Success` \u00e2\u20ac\u201d atualizacao com sucesso\n4. `TestUserHandler_UpdateUser_NotFound` \u00e2\u20ac\u201d usuario nao encontrado\n5. `TestUserHandler_UpdateUser_InvalidPhone` \u00e2\u20ac\u201d telefone invalido\n\n**Criterio de aceite:**\n- Mock atualizado com metodo `UpdateUser`\n- Testes de phone no CreateUser e GetUser\n- Testes de UpdateUser cobrindo sucesso e erros\n- Todos os testes passam: `CGO_ENABLED=1 go test ./internal/handler/grpc/... -v`\n\n**Dependencias:** TASK-8\n\n---\n\n### TASK-12: Teste de integracao e validacao final\n\n**Camada:** Validacao\n**Descricao:**\nExecutar build e suite completa de testes para garantir que tudo funciona junto.\n\n**Comandos:**\n```bash\nmake build\nmake test\n```\n\n**Criterio de aceite:**\n- Build compila sem erros\n- Todos os testes passam\n- Nenhuma regressao nos testes existentes\n\n**Dependencias:** TASK-10, TASK-11\n\n---\n\n## 4. Grafo de Dependencias\n\n```\nTASK-1 (migracao SQL)\n \u00e2\u201d\u201d\u00e2\u201d\u20ac> TASK-2 (queries SQLC)\n \u00e2\u201d\u201d\u00e2\u201d\u20ac> TASK-3 (make generate - SQLC)\n \u00e2\u201d\u201d\u00e2\u201d\u20ac> TASK-4 (repository)\n \u00e2\u201d\u201d\u00e2\u201d\u20ac> TASK-5 (service)\n \u00e2\u201d\u0153\u00e2\u201d\u20ac> TASK-8 (handler) [+ TASK-7]\n \u00e2\u201d\u201d\u00e2\u201d\u20ac> TASK-10 (testes service)\n\nTASK-6 (proto) [paralelo com TASK-1..5]\n \u00e2\u201d\u201d\u00e2\u201d\u20ac> TASK-7 (make generate - proto)\n \u00e2\u201d\u201d\u00e2\u201d\u20ac> TASK-8 (handler)\n \u00e2\u201d\u0153\u00e2\u201d\u20ac> TASK-9 (config auth)\n \u00e2\u201d\u201d\u00e2\u201d\u20ac> TASK-11 (testes handler)\n\nTASK-10 + TASK-11\n \u00e2\u201d\u201d\u00e2\u201d\u20ac> TASK-12 (validacao final)\n```\n\n## 5. Ordem de Execucao Sugerida\n\n| Fase | Tasks | Descricao |\n|------|-------|-----------|\n| **Fase 1** | TASK-1, TASK-6 | Migracao SQL + Proto (paralelo) |\n| **Fase 2** | TASK-2 | Queries SQLC |\n| **Fase 3** | TASK-3, TASK-7 | Regenerar codigo (SQLC + Proto) \u00e2\u20ac\u201d executar `make generate` uma unica vez |\n| **Fase 4** | TASK-4 | Repository |\n| **Fase 5** | TASK-5 | Service |\n| **Fase 6** | TASK-8, TASK-9 | Handler + Config |\n| **Fase 7** | TASK-10, TASK-11 | Testes (paralelo) |\n| **Fase 8** | TASK-12 | Validacao final |\n\n## 6. Riscos e Observacoes\n\n1. **Numeracao Proto:** A numeracao dos campos proto existentes NAO pode ser alterada. O campo `phone` deve usar o proximo numero livre em cada mensagem. Isso e critico para compatibilidade.\n\n2. **Migracao imutavel:** A migracao 001 NAO deve ser editada. Sempre criar nova migracao (002).\n\n3. **Codigo gerado:** Nunca editar arquivos em `gen/` ou `internal/db/sqlc/*.go`. Sempre editar os fontes (`.proto`, `.sql`) e rodar `make generate`.\n\n4. **Campo opcional:** O campo `telefone` e `TEXT` nullable no SQLite. No Go, o SQLC pode gerar como `sql.NullString` \u00e2\u20ac\u201d verificar apos geracao e ajustar mapeamento no repository conforme necessario.\n\n5. **UpdateUser e novo:** O proto atual nao tem `UpdateUser`. E necessario criar o RPC, mensagens e implementar em todas as camadas. A SPEC_TECH menciona \"UpdateUser\" mas o codebase atual nao possui esse endpoint.\n\n6. **`make generate`:** Os comandos `make generate` das TASK-3 e TASK-7 podem ser executados em uma unica chamada se o Makefile gerar tanto SQLC quanto Proto.\n"}], "grading": {"eval_id": 2, "eval_name": "spec-tech-simples-alteracao-pontual", "configuration": "without_skill", "expectations": [{"text": "Gera um numero proporcional de tasks (entre 3 e 6) \u00e2\u20ac\u201d nao over-engineers com tasks demais para uma alteracao simples", "passed": false, "evidence": "Gerou 12 tasks para uma feature simples de adicionar um campo. Over-engineered com tasks separadas para 'regenerar codigo SQLC' (TASK-3), 'regenerar codigo Proto' (TASK-7), e 'configurar UpdateUser como rota protegida' (TASK-9) que poderiam ser subtasks."}, {"text": "Respeita a ordem de dependencias das camadas (migracao > SQLC > repository > service > handler > proto)", "passed": true, "evidence": "Grafo de dependencias mostra TASK-1(migracao)->TASK-2(SQLC)->TASK-3(generate)->TASK-4(repo)->TASK-5(service)->TASK-8(handler). Proto em paralelo (TASK-6). Ordem correta."}, {"text": "Mapeia a US-10 na tabela de rastreabilidade do task_plan", "passed": false, "evidence": "Nao ha tabela de rastreabilidade mapeando US-10 para tasks. A US-10 nao e mencionada no task plan."}, {"text": "Cada task segue o template completo (secoes 1-8) ou marca secoes N/A com justificativa", "passed": false, "evidence": "Tasks usam formato ad-hoc (Camada, Arquivo, Descricao, Criterio de aceite, Dependencias) em vez do template oficial com secoes 1-8 (Identificacao, Objetivo, Descricao Detalhada, Aceite Tecnico, Arquivos Impactados, Testes, Notas, Checklist)."}, {"text": "Salva task_plan.md como documento de REFERENCIA (sem corpo detalhado de tasks dentro dele)", "passed": false, "evidence": "Todo o conteudo detalhado das tasks esta inline no mesmo documento. Nao ha separacao entre task_plan (referencia) e tasks individuais."}]}}, {"id": "eval-3-sem-user-stories-with_skill", "prompt": "Gere o TASK PLAN para esta feature. Segue o SPEC_TECH aprovado:\n\n---\n# SPEC_TECH \u00e2\u20ac\u201d Cache de Cardapio v1\n\n## 1. Identificacao\n| Campo | Valor |\n|-------|-------|\n| **Feature/Projeto** | Cache de Cardapio em Memoria |\n| **Versao** | v1 |\n| **Data** | 2026-03-07 |\n\n## 2. Resumo Tecnico\nImplementar cache in-memory (sync.Map) para o cardapio de produtos, evitando queries repetidas ao SQLite. TTL configuravel via config.yaml.\n\n## 3. Definicoes Tecnicas\n\n### 3.1 Novo Pacote\n- `internal/infra/cache/` com interface `Cache[K, V]` e implementacao `InMemoryCache`\n- TTL configuravel via `cache.ttl_seconds` no config.yaml\n- Invalidacao manual via metodo `Invalidate(key)`\n\n### 3.2 Service\n- ProductService passa a consultar cache antes do repository\n- Cache miss: busca no repository e popula cache\n- Operacoes de escrita (Create, Update, Delete) invalidam a entrada correspondente\n\n### 3.3 Config\n- Novo campo `cache.ttl_seconds` (padrao: 300)\n\n### 3.4 DI\n- Registrar modulo cache no FX\n---", "eval_id": 3, "outputs": [{"name": "response.md", "type": "text", "content": "# Resposta do Task Plan Expert \u00e2\u20ac\u201d Interacao Inicial\n\n## 1. Extra\u00c3\u00a7\u00c3\u00a3o do Nome da Feature\n\nIdentifiquei o nome da feature na secao \"1. Identificacao\" do SPEC_TECH:\n\n- **Feature/Projeto**: Cache de Cardapio em Memoria\n- **Nome normalizado (kebab-case)**: `cache-de-cardapio`\n- **Versao**: v1\n- **Diretorio de saida**: `docs/cache-de-cardapio/v1/`\n\n---\n\n## 2. Analise Obrigatoria do Projeto (Codebase)\n\nAntes de planejar qualquer task, explorei o codebase e identifiquei o seguinte:\n\n### O que JA existe no projeto:\n| Elemento | Status | Localizacao |\n|----------|--------|-------------|\n| Estrutura Clean Architecture (handler/service/repository) | Existe | `internal/` |\n| Injecao de dependencia via Uber FX | Existe | `internal/infra/di/fx.go` |\n| Config via Viper + `config.yaml` | Existe | `internal/infra/config/config.go`, `configs/config.yaml` |\n| User module completo (handler, service, repo, tests) | Existe | `internal/handler/grpc/`, `internal/service/`, `internal/repository/` |\n| SQLC para queries tipadas | Existe | `internal/db/sqlc/` |\n| Migracao de usuarios | Existe | `internal/db/migrations/001_create_usuarios.sql` |\n| Testes com testify/mock (table-driven) | Existe | `internal/service/user_service_test.go` |\n| gRPC server com interceptors | Existe | `internal/infra/grpc/server.go` |\n\n### O que NAO existe (precisa ser criado):\n| Elemento | Status |\n|----------|--------|\n| Modulo de Produto (Product) \u00e2\u20ac\u201d service, repository, handler, proto, migration, queries | **NAO existe** |\n| Pacote de cache (`internal/infra/cache/`) | **NAO existe** |\n| Campo `cache.ttl_seconds` no config | **NAO existe** |\n| Tabela `produtos` no banco | **NAO existe** |\n\n### Observacao Critica\n\nO SPEC_TECH fala em \"cache de cardapio de produtos\" e menciona que o `ProductService` passara a consultar o cache antes do repository. Porem, **nao existe nenhum modulo de Produto no projeto**. Nao ha proto, migration, queries SQLC, repository, service ou handler de produtos.\n\nIsso significa que o TASK PLAN precisara incluir tasks para criar o modulo de Produto completo OU o SPEC_TECH assume que esse modulo ja existe/sera criado em outra feature. Essa duvida sera direcionada ao usuario (veja secao 3).\n\n---\n\n## 3. Ponto de Atencao \u00e2\u20ac\u201d User Stories e PRD Ausentes\n\nO SPEC_TECH fornecido **nao contem User Stories (US-XX)** e **nao referencia nenhum PRD**.\n\nNo framework SDD, o TASK PLAN inclui uma **tabela de rastreabilidade obrigatoria** que mapeia User Stories do PRD para definicoes tecnicas do SPEC_TECH e para tasks correspondentes (Guardrail #8: \"Rastreabilidade obrigatoria \u00e2\u20ac\u201d toda User Story do PRD deve ter pelo menos uma task correspondente\").\n\n**Pergunta que faria ao usuario:**\n\n> O SPEC_TECH fornecido nao contem User Stories (US-XX) e nao referencia um PRD. No framework SDD, a rastreabilidade User Stories -> Tasks e obrigatoria no TASK PLAN.\n>\n> Temos duas opcoes:\n>\n> 1. **Voce fornece o PRD ou as User Stories** relacionadas a esta feature, e eu incluo a rastreabilidade completa no TASK PLAN.\n> 2. **Seguimos sem PRD/User Stories**, e a secao de rastreabilidade do TASK PLAN sera preenchida com \"N/A \u00e2\u20ac\u201d SPEC_TECH fornecido sem PRD/User Stories associadas\". Neste caso, a rastreabilidade sera feita apenas entre definicoes tecnicas do SPEC e tasks.\n>\n> Qual opcao prefere?\n\n---\n\n## 4. Ponto de Atencao \u00e2\u20ac\u201d Dependencia do Modulo de Produto\n\nAlem da questao de User Stories, identifiquei uma dependencia critica:\n\n**Pergunta que faria ao usuario (apos resposta da pergunta anterior):**\n\n> O SPEC_TECH menciona que o `ProductService` passara a consultar o cache antes do repository, e que operacoes de escrita (Create, Update, Delete) invalidarao entradas do cache. Porem, **nao existe nenhum modulo de Produto no codebase atual** \u00e2\u20ac\u201d nao ha proto, migration, queries, repository, service ou handler de produtos.\n>\n> Duas opcoes:\n>\n> 1. **O escopo deste TASK PLAN inclui a criacao do modulo de Produto completo** (migration, queries SQLC, repository, service, handler gRPC, proto) alem do cache. Isso aumenta significativamente o numero de tasks.\n> 2. **O modulo de Produto sera criado em outra feature/SPEC_TECH**, e este TASK PLAN assume que ele ja existira quando as tasks forem executadas. Neste caso, incluo apenas as tasks de cache + integracao com o ProductService.\n>\n> Qual abordagem seguir?\n\n---\n\n## 5. Confirmacao do Nome e Inicio\n\nApos as respostas acima, a proxima interacao seria:\n\n> Obrigado! Vamos iniciar o TASK PLAN para **Cache de Cardapio em Memoria (v1)**. Podemos iniciar a definicao macro das fases?\n\n---\n\n## Resumo da Interacao Inicial\n\n| Etapa | Status |\n|-------|--------|\n| Leitura do SPEC_TECH | Concluido |\n| Extracao do nome da feature | Concluido: `cache-de-cardapio` |\n| Analise do codebase | Concluido |\n| Identificacao de User Stories/PRD ausentes | Concluido \u00e2\u20ac\u201d pergunta formulada |\n| Identificacao de dependencia critica (modulo Produto) | Concluido \u00e2\u20ac\u201d pergunta formulada |\n| Confirmacao do nome e inicio das fases | Aguardando respostas anteriores |\n\n**Proximo passo**: Aguardar respostas do usuario para as duas perguntas acima antes de avancar para a definicao macro das fases.\n"}], "grading": {"eval_id": 3, "eval_name": "spec-tech-sem-user-stories-explicitas", "configuration": "with_skill", "expectations": [{"text": "Detecta a ausencia de User Stories (US-XX) e PRD referenciado no SPEC_TECH", "passed": true, "evidence": "Secao 3 explicita: 'O SPEC_TECH fornecido nao contem User Stories (US-XX) e nao referencia nenhum PRD.' Tambem identificou a dependencia critica do modulo de Produto inexistente."}, {"text": "PERGUNTA ao usuario sobre User Stories / PRD em vez de inventar ou ignorar", "passed": true, "evidence": "Formulou pergunta clara com 2 opcoes: (1) usuario fornece PRD/US ou (2) seguir sem, com rastreabilidade N/A. Aguarda resposta antes de avancar."}, {"text": "NAO inventa User Stories ficticias (guardrail: nunca invente informacoes)", "passed": true, "evidence": "Nenhuma User Story ficticia foi inventada. A resposta explicitamente identifica a ausencia e pergunta ao usuario."}, {"text": "Extrai corretamente o nome da feature como 'cache-de-cardapio' ou similar em kebab-case", "passed": true, "evidence": "Extraiu 'Cache de Cardapio em Memoria' e normalizou para 'cache-de-cardapio' em kebab-case."}, {"text": "Segue o processo interativo (uma pergunta por vez, confirma nome antes de prosseguir)", "passed": true, "evidence": "Seguiu sequencia: (1) extraiu nome, (2) perguntou sobre US/PRD ausentes, (3) perguntou sobre dependencia do modulo Produto. Indicou que confirmacao do nome e inicio das fases aguardam respostas anteriores."}]}}, {"id": "eval-3-sem-user-stories-without_skill", "prompt": "Gere o TASK PLAN para esta feature. Segue o SPEC_TECH aprovado:\n\n---\n# SPEC_TECH \u00e2\u20ac\u201d Cache de Cardapio v1\n\n## 1. Identificacao\n| Campo | Valor |\n|-------|-------|\n| **Feature/Projeto** | Cache de Cardapio em Memoria |\n| **Versao** | v1 |\n| **Data** | 2026-03-07 |\n\n## 2. Resumo Tecnico\nImplementar cache in-memory (sync.Map) para o cardapio de produtos, evitando queries repetidas ao SQLite. TTL configuravel via config.yaml.\n\n## 3. Definicoes Tecnicas\n\n### 3.1 Novo Pacote\n- `internal/infra/cache/` com interface `Cache[K, V]` e implementacao `InMemoryCache`\n- TTL configuravel via `cache.ttl_seconds` no config.yaml\n- Invalidacao manual via metodo `Invalidate(key)`\n\n### 3.2 Service\n- ProductService passa a consultar cache antes do repository\n- Cache miss: busca no repository e popula cache\n- Operacoes de escrita (Create, Update, Delete) invalidam a entrada correspondente\n\n### 3.3 Config\n- Novo campo `cache.ttl_seconds` (padrao: 300)\n\n### 3.4 DI\n- Registrar modulo cache no FX\n---", "eval_id": 3, "outputs": [{"name": "response.md", "type": "text", "content": "# TASK PLAN -- Cache de Cardapio v1\n\n## 1. Identificacao\n\n| Campo | Valor |\n|--------------------|--------------------------------|\n| **Feature/Projeto** | Cache de Cardapio em Memoria |\n| **Versao** | v1 |\n| **Data** | 2026-03-07 |\n| **SPEC_TECH** | Cache de Cardapio v1 |\n| **Branch base** | user-feature |\n\n---\n\n## 2. Observacoes Preliminares\n\nO SPEC_TECH nao referencia User Stories nem PRD. Isso implica que:\n- Nao ha criterios de aceitacao formais vindos de um PRD.\n- Os criterios de aceitacao serao derivados diretamente das definicoes tecnicas do SPEC_TECH.\n- Nao ha modulo de Product/Produto implementado no codebase atual. A API possui apenas o modulo User. Portanto, **antes de implementar o cache do cardapio, e necessario que o modulo Product (repository, service, handler, proto, migrations, queries SQLC) ja exista**. Este task plan assume que o modulo Product sera implementado como pre-requisito ou em paralelo.\n\n---\n\n## 3. Pre-requisitos\n\nAntes de iniciar as tasks deste plano, os seguintes itens devem estar prontos:\n\n| # | Pre-requisito | Status |\n|---|---------------|--------|\n| P1 | Modulo Product completo (proto, migration, queries SQLC, repository, service, handler) | Pendente -- nao existe no codebase |\n| P2 | Queries SQLC para produtos geradas (`make sqlc`) | Pendente |\n| P3 | Proto de produto gerado (`make proto`) | Pendente |\n\n> **Nota:** Se o modulo Product nao existir, as Tasks 4 e 5 nao podem ser implementadas. As Tasks 1-3 (infraestrutura de cache) sao independentes e podem ser feitas antes.\n\n---\n\n## 4. Tasks\n\n### Task 1 -- Adicionar configuracao de cache ao config.yaml e struct Config\n\n**Objetivo:** Expor o campo `cache.ttl_seconds` na configuracao da aplicacao.\n\n**Arquivos afetados:**\n- `configs/config.yaml`\n- `internal/infra/config/config.go`\n- `internal/infra/config/config_test.go`\n\n**Subtasks:**\n\n| # | Descricao | Detalhe |\n|---|-----------|---------|\n| 1.1 | Adicionar secao `cache` ao `configs/config.yaml` | Adicionar `cache.ttl_seconds: 300` (valor padrao) |\n| 1.2 | Adicionar campo `CacheTTLSeconds int` na struct `Config` | Em `internal/infra/config/config.go` |\n| 1.3 | Popular o campo via Viper | `v.GetInt(\"cache.ttl_seconds\")` com fallback para 300 se nao configurado |\n| 1.4 | Adicionar teste unitario | Verificar que o valor padrao e 300 e que override via env (`APP_CACHE_TTL_SECONDS`) funciona |\n\n**Criterios de aceite:**\n- `Config.CacheTTLSeconds` retorna 300 quando nao configurado.\n- `Config.CacheTTLSeconds` respeita valor do YAML e override via variavel de ambiente.\n- Testes passam.\n\n**Estimativa:** P (pequena)\n\n---\n\n### Task 2 -- Criar pacote de cache generico\n\n**Objetivo:** Implementar `internal/infra/cache/` com interface generica `Cache[K, V]` e implementacao `InMemoryCache` usando `sync.Map`.\n\n**Arquivos a criar:**\n- `internal/infra/cache/cache.go` -- interface e implementacao\n- `internal/infra/cache/cache_test.go` -- testes unitarios\n- `internal/infra/cache/fx.go` -- modulo FX\n\n**Subtasks:**\n\n| # | Descricao | Detalhe |\n|---|-----------|---------|\n| 2.1 | Definir interface `Cache[K comparable, V any]` | Metodos: `Get(key K) (V, bool)`, `Set(key K, value V)`, `Invalidate(key K)`, `InvalidateAll()` |\n| 2.2 | Implementar `InMemoryCache[K, V]` | Usar `sync.Map` internamente. Cada entrada armazena o valor e o timestamp de insercao. No `Get`, verificar se `time.Since(timestamp) > ttl`; se sim, deletar e retornar `false`. |\n| 2.3 | Construtor `NewInMemoryCache[K, V](ttl time.Duration) *InMemoryCache[K, V]` | Recebe TTL como parametro |\n| 2.4 | Escrever testes unitarios | Testar: Get/Set basico, expiricao por TTL, Invalidate de chave unica, InvalidateAll |\n| 2.5 | Criar modulo FX (`fx.go`) | Nao registrar provider generico diretamente -- o modulo FX servira como ponto de importacao. A instanciacao concreta sera feita no service ou via factory (ver Task 4). |\n\n**Decisoes tecnicas:**\n- `sync.Map` e thread-safe e adequado para cenarios read-heavy (cache de cardapio).\n- Cada entry no map sera um struct `cacheEntry[V]{ value V, createdAt time.Time }`.\n- A verificacao de TTL e lazy (no momento do `Get`), sem goroutine de limpeza periodica em v1.\n\n**Criterios de aceite:**\n- Interface `Cache[K, V]` definida e exportada.\n- `InMemoryCache` implementa a interface corretamente.\n- TTL e respeitado: itens expirados nao sao retornados por `Get`.\n- `Invalidate` remove entrada especifica; `InvalidateAll` limpa todo o cache.\n- Testes passam com cobertura dos cenarios listados.\n\n**Estimativa:** M (media)\n\n---\n\n### Task 3 -- Registrar modulo cache no FX (DI)\n\n**Objetivo:** Integrar o modulo de cache na composicao de dependencias da aplicacao.\n\n**Arquivos afetados:**\n- `internal/infra/di/fx.go`\n- `internal/infra/cache/fx.go` (criado na Task 2)\n\n**Subtasks:**\n\n| # | Descricao | Detalhe |\n|---|-----------|---------|\n| 3.1 | Criar factory function para cache de produtos | `NewProductCache(cfg *config.Config) *InMemoryCache[string, *repository.Product]` -- usa `cfg.CacheTTLSeconds` para definir o TTL |\n| 3.2 | Registrar no modulo FX do cache | `fx.Provide(NewProductCache)` |\n| 3.3 | Adicionar `cache.Module` ao `AppModule()` em `internal/infra/di/fx.go` | Posicionar entre `config.Module` e `repository.Module` na lista de modulos |\n| 3.4 | Atualizar teste de DI | Verificar que `fx.ValidateApp` passa com o novo modulo |\n\n**Dependencias:** Task 1 (config), Task 2 (pacote cache), Pre-requisito P1 (modulo Product -- para o tipo `repository.Product`)\n\n**Criterios de aceite:**\n- `cache.Module` esta registrado em `AppModule()`.\n- Aplicacao inicializa sem erros com o novo modulo.\n- Teste de validacao FX passa.\n\n**Estimativa:** P (pequena)\n\n---\n\n### Task 4 -- Integrar cache no ProductService\n\n**Objetivo:** Modificar o `ProductService` para consultar cache antes do repository, e invalidar cache em operacoes de escrita.\n\n**Arquivos afetados:**\n- `internal/service/product_service.go` (a ser criado no pre-requisito ou modificado se ja existir)\n- `internal/service/product_service_test.go`\n\n**Subtasks:**\n\n| # | Descricao | Detalhe |\n|---|-----------|---------|\n| 4.1 | Adicionar dependencia de cache ao `productService` | Novo campo `cache cache.Cache[string, *repository.Product]` na struct |\n| 4.2 | Atualizar construtor `NewProductService` | Receber `*InMemoryCache[string, *repository.Product]` como parametro |\n| 4.3 | Implementar cache-aside no `GetProduct` | 1) Consultar `cache.Get(id)`. 2) Se hit, retornar direto. 3) Se miss, buscar no repository, chamar `cache.Set(id, product)`, retornar. |\n| 4.4 | Invalidar cache em `CreateProduct` | Apos insert no repository, chamar `cache.Invalidate(id)` (ou nao popular para evitar inconsistencia) |\n| 4.5 | Invalidar cache em `UpdateProduct` | Apos update no repository, chamar `cache.Invalidate(id)` |\n| 4.6 | Invalidar cache em `DeleteProduct` | Apos delete no repository, chamar `cache.Invalidate(id)` |\n| 4.7 | Adicionar logs de cache hit/miss | `s.logger.Debug(\"cache hit para produto\", zap.String(\"id\", id))` e `s.logger.Debug(\"cache miss para produto\", zap.String(\"id\", id))` |\n| 4.8 | Escrever testes unitarios | Testar: cache hit retorna sem chamar repo, cache miss chama repo e popula cache, create/update/delete invalidam cache |\n\n**Dependencias:** Task 2 (pacote cache), Task 3 (DI), Pre-requisito P1 (modulo Product)\n\n**Criterios de aceite:**\n- `GetProduct` retorna do cache quando disponivel (sem query ao banco).\n- `GetProduct` busca do repository em cache miss e popula o cache.\n- `CreateProduct`, `UpdateProduct`, `DeleteProduct` invalidam a entrada correspondente no cache.\n- Logs de debug indicam hit/miss.\n- Testes unitarios com mock de cache e mock de repository passam.\n- Nenhuma regressao nos testes existentes.\n\n**Estimativa:** M (media)\n\n---\n\n### Task 5 -- Testes de integracao e validacao final\n\n**Objetivo:** Garantir que o fluxo completo funciona end-to-end e que o cache se comporta corretamente sob uso real.\n\n**Arquivos afetados:**\n- `internal/service/product_service_test.go` (testes adicionais)\n\n**Subtasks:**\n\n| # | Descricao | Detalhe |\n|---|-----------|---------|\n| 5.1 | Teste de cenario completo | Criar produto -> Get (cache miss, popula) -> Get novamente (cache hit) -> Update (invalida) -> Get (cache miss novamente) |\n| 5.2 | Teste de expiracao por TTL | Criar produto -> Get (popula cache com TTL curto) -> esperar expiracao -> Get (cache miss) |\n| 5.3 | Teste de concorrencia | Multiplas goroutines fazendo Get simultaneo no mesmo produto -- garantir que nao ha race condition |\n| 5.4 | Rodar `make test` completo | Garantir que nenhum teste existente quebrou |\n| 5.5 | Rodar `make build` | Garantir que a compilacao passa |\n\n**Dependencias:** Tasks 1-4 completas\n\n**Criterios de aceite:**\n- Todos os cenarios de teste passam.\n- `go test -race ./internal/...` nao detecta race conditions.\n- `make build` e `make test` passam sem erros.\n\n**Estimativa:** M (media)\n\n---\n\n## 5. Ordem de Execucao e Dependencias\n\n```\nTask 1 (Config) \u00e2\u201d\u20ac\u00e2\u201d\u20ac\u00e2\u201d\u20ac\u00e2\u201d\u20ac\u00e2\u201d\u20ac\u00e2\u201d\ufffd\n \u00e2\u201d\u0153\u00e2\u201d\u20ac\u00e2\u201d\u20ac> Task 3 (DI) \u00e2\u201d\u20ac\u00e2\u201d\u20ac> Task 4 (Service) \u00e2\u201d\u20ac\u00e2\u201d\u20ac> Task 5 (Validacao)\nTask 2 (Cache pkg) \u00e2\u201d\u20ac\u00e2\u201d\u20ac\u00e2\u201d\u02dc\n```\n\n- Tasks 1 e 2 podem ser executadas **em paralelo**.\n- Task 3 depende de Tasks 1 e 2.\n- Task 4 depende de Task 3 e do pre-requisito P1 (modulo Product).\n- Task 5 depende de Task 4.\n\n---\n\n## 6. Resumo de Estimativas\n\n| Task | Descricao | Estimativa | Dependencias |\n|------|-----------|------------|--------------|\n| 1 | Config de cache | P (pequena) | Nenhuma |\n| 2 | Pacote cache generico | M (media) | Nenhuma |\n| 3 | Registro no FX (DI) | P (pequena) | Tasks 1, 2, P1 |\n| 4 | Integracao no ProductService | M (media) | Task 3, P1 |\n| 5 | Testes e validacao | M (media) | Task 4 |\n\n**Esforco total estimado:** ~1.5 a 2 dias de desenvolvimento.\n\n---\n\n## 7. Riscos e Consideracoes\n\n| # | Risco | Mitigacao |\n|---|-------|-----------|\n| R1 | Modulo Product nao existe no codebase -- bloqueia Tasks 3-5 | Implementar modulo Product antes ou em paralelo. Tasks 1-2 sao independentes. |\n| R2 | `sync.Map` nao suporta generics nativamente (usa `any` internamente) | Wrapper tipado com type assertions no `InMemoryCache`. Testes garantem type safety. |\n| R3 | Lazy expiration pode acumular entradas expiradas em memoria | Aceitavel em v1 para um cardapio pequeno. Em v2, considerar goroutine de limpeza periodica. |\n| R4 | Cache pode servir dados stale em cenarios de escrita concorrente | Invalidacao sincrona no mesmo request de escrita mitiga isso. TTL garante convergencia eventual. |\n| R5 | Sem metricas de cache (hit rate, tamanho) | Fora de escopo em v1. Considerar para v2. |\n\n---\n\n## 8. Arquivos Criados/Modificados (Resumo)\n\n### Novos arquivos:\n- `internal/infra/cache/cache.go`\n- `internal/infra/cache/cache_test.go`\n- `internal/infra/cache/fx.go`\n\n### Arquivos modificados:\n- `configs/config.yaml` -- adicionar secao `cache`\n- `internal/infra/config/config.go` -- adicionar campo `CacheTTLSeconds`\n- `internal/infra/config/config_test.go` -- testes do novo campo\n- `internal/infra/di/fx.go` -- registrar `cache.Module`\n- `internal/infra/di/fx_test.go` -- atualizar teste de validacao\n- `internal/service/product_service.go` -- integrar cache (requer que o arquivo exista)\n- `internal/service/product_service_test.go` -- testes de cache no service\n"}], "grading": {"eval_id": 3, "eval_name": "spec-tech-sem-user-stories-explicitas", "configuration": "without_skill", "expectations": [{"text": "Detecta a ausencia de User Stories (US-XX) e PRD referenciado no SPEC_TECH", "passed": true, "evidence": "Secao 2 'Observacoes Preliminares' nota: 'O SPEC_TECH nao referencia User Stories nem PRD.' Tambem detectou que o modulo Product nao existe."}, {"text": "PERGUNTA ao usuario sobre User Stories / PRD em vez de inventar ou ignorar", "passed": false, "evidence": "Nao perguntou ao usuario. Apenas notou a ausencia como observacao e prosseguiu gerando o plano completo com 5 tasks sem interacao."}, {"text": "NAO inventa User Stories ficticias (guardrail: nunca invente informacoes)", "passed": true, "evidence": "Nao inventou User Stories. Derivou criterios diretamente do SPEC_TECH."}, {"text": "Extrai corretamente o nome da feature como 'cache-de-cardapio' ou similar em kebab-case", "passed": false, "evidence": "Usou 'Cache de Cardapio em Memoria' como titulo mas nao normalizou para kebab-case. Nenhuma convencao de nomenclatura aplicada."}, {"text": "Segue o processo interativo (uma pergunta por vez, confirma nome antes de prosseguir)", "passed": false, "evidence": "Nenhum processo interativo. Gerou o plano completo em um unico turno sem perguntas ao usuario."}]}}], "previous_feedback": {}, "previous_outputs": {}, "benchmark": {"skill_name": "sdd-task-plan-expert", "iteration": 1, "configurations": [{"name": "with_skill", "pass_rate": 0.933, "mean_tokens": 55027, "stddev_tokens": 10217, "mean_duration_seconds": 184.9, "stddev_duration_seconds": 85.7, "evals": [{"eval_name": "happy-path-spec-tech-usuario", "pass_rate": 0.8, "passed": 4, "total": 5, "tokens": 61221, "duration_seconds": 138.2, "failed_assertions": ["Le as rules do projeto (.claude/rules/ e CLAUDE.md) ANTES de gerar tasks"]}, {"eval_name": "spec-tech-simples-alteracao-pontual", "pass_rate": 1.0, "passed": 5, "total": 5, "tokens": 60596, "duration_seconds": 286.6, "failed_assertions": []}, {"eval_name": "spec-tech-sem-user-stories-explicitas", "pass_rate": 1.0, "passed": 5, "total": 5, "tokens": 43263, "duration_seconds": 130.0, "failed_assertions": []}]}, {"name": "without_skill", "pass_rate": 0.333, "mean_tokens": 38834, "stddev_tokens": 3912, "mean_duration_seconds": 151.2, "stddev_duration_seconds": 14.5, "evals": [{"eval_name": "happy-path-spec-tech-usuario", "pass_rate": 0.2, "passed": 1, "total": 5, "tokens": 40363, "duration_seconds": 141.3, "failed_assertions": ["Extrai o nome da feature do SPEC_TECH e confirma com o usuario antes de prosseguir", "Faz apenas UMA pergunta por vez", "Propoe macro-fases de alto nivel e aguarda validacao antes de criar tasks", "Le as rules do projeto (.claude/rules/ e CLAUDE.md) ANTES de gerar tasks"]}, {"eval_name": "spec-tech-simples-alteracao-pontual", "pass_rate": 0.2, "passed": 1, "total": 5, "tokens": 41818, "duration_seconds": 167.6, "failed_assertions": ["Gera um numero proporcional de tasks (entre 3 e 6)", "Mapeia a US-10 na tabela de rastreabilidade do task_plan", "Cada task segue o template completo (secoes 1-8)", "Salva task_plan.md como documento de REFERENCIA"]}, {"eval_name": "spec-tech-sem-user-stories-explicitas", "pass_rate": 0.4, "passed": 2, "total": 5, "tokens": 34322, "duration_seconds": 144.7, "failed_assertions": ["PERGUNTA ao usuario sobre User Stories / PRD em vez de inventar ou ignorar", "Extrai corretamente o nome da feature em kebab-case", "Segue o processo interativo (uma pergunta por vez)"]}]}], "delta": {"pass_rate_improvement": "+60.0pp", "tokens_overhead": "+41.7%", "duration_overhead": "+22.3%"}}};
650
-
651
- // ---- State ----
652
- let feedbackMap = {}; // run_id -> feedback text
653
- let currentIndex = 0;
654
- let visitedRuns = new Set();
655
-
656
- // ---- Init ----
657
- async function init() {
658
- // Load saved feedback from server — but only if this isn't a fresh
659
- // iteration (indicated by previous_feedback being present). When
660
- // previous feedback exists, the feedback.json on disk is stale from
661
- // the prior iteration and should not pre-fill the textareas.
662
- const hasPrevious = Object.keys(EMBEDDED_DATA.previous_feedback || {}).length > 0
663
- || Object.keys(EMBEDDED_DATA.previous_outputs || {}).length > 0;
664
- if (!hasPrevious) {
665
- try {
666
- const resp = await fetch("/api/feedback");
667
- const data = await resp.json();
668
- if (data.reviews) {
669
- for (const r of data.reviews) feedbackMap[r.run_id] = r.feedback;
670
- }
671
- } catch { /* first run, no feedback yet */ }
672
- }
673
-
674
- document.getElementById("skill-name").textContent = EMBEDDED_DATA.skill_name;
675
- showRun(0);
676
-
677
- // Wire up feedback auto-save
678
- const textarea = document.getElementById("feedback");
679
- let saveTimeout = null;
680
- textarea.addEventListener("input", () => {
681
- clearTimeout(saveTimeout);
682
- document.getElementById("feedback-status").textContent = "";
683
- saveTimeout = setTimeout(() => saveCurrentFeedback(), 800);
684
- });
685
- }
686
-
687
- // ---- Navigation ----
688
- function navigate(delta) {
689
- const newIndex = currentIndex + delta;
690
- if (newIndex >= 0 && newIndex < EMBEDDED_DATA.runs.length) {
691
- saveCurrentFeedback();
692
- showRun(newIndex);
693
- }
694
- }
695
-
696
- function updateNavButtons() {
697
- document.getElementById("prev-btn").disabled = currentIndex === 0;
698
- document.getElementById("next-btn").disabled =
699
- currentIndex === EMBEDDED_DATA.runs.length - 1;
700
- }
701
-
702
- // ---- Show a run ----
703
- function showRun(index) {
704
- currentIndex = index;
705
- const run = EMBEDDED_DATA.runs[index];
706
-
707
- // Progress
708
- document.getElementById("progress").textContent =
709
- `${index + 1} of ${EMBEDDED_DATA.runs.length}`;
710
-
711
- // Prompt
712
- document.getElementById("prompt-text").textContent = run.prompt;
713
-
714
- // Config badge
715
- const badge = document.getElementById("config-badge");
716
- const configMatch = run.id.match(/(with_skill|without_skill|new_skill|old_skill)/);
717
- if (configMatch) {
718
- const config = configMatch[1];
719
- const isBaseline = config === "without_skill" || config === "old_skill";
720
- badge.textContent = config.replace(/_/g, " ");
721
- badge.className = "config-badge " + (isBaseline ? "config-baseline" : "config-primary");
722
- badge.style.display = "inline-block";
723
- } else {
724
- badge.style.display = "none";
725
- }
726
-
727
- // Outputs
728
- renderOutputs(run);
729
-
730
- // Previous outputs
731
- renderPrevOutputs(run);
732
-
733
- // Grades
734
- renderGrades(run);
735
-
736
- // Previous feedback
737
- const prevFb = (EMBEDDED_DATA.previous_feedback || {})[run.id];
738
- const prevEl = document.getElementById("prev-feedback");
739
- if (prevFb) {
740
- document.getElementById("prev-feedback-text").textContent = prevFb;
741
- prevEl.style.display = "block";
742
- } else {
743
- prevEl.style.display = "none";
744
- }
745
-
746
- // Feedback
747
- document.getElementById("feedback").value = feedbackMap[run.id] || "";
748
- document.getElementById("feedback-status").textContent = "";
749
-
750
- updateNavButtons();
751
-
752
- // Track visited runs and promote done button when all visited
753
- visitedRuns.add(index);
754
- const doneBtn = document.getElementById("done-btn");
755
- if (visitedRuns.size >= EMBEDDED_DATA.runs.length) {
756
- doneBtn.classList.add("ready");
757
- }
758
-
759
- // Scroll main content to top
760
- document.querySelector(".main").scrollTop = 0;
761
- }
762
-
763
- // ---- Render outputs ----
764
- function renderOutputs(run) {
765
- const container = document.getElementById("outputs-body");
766
- container.innerHTML = "";
767
-
768
- const outputs = run.outputs || [];
769
- if (outputs.length === 0) {
770
- container.innerHTML = '<div class="empty-state">No output files</div>';
771
- return;
772
- }
773
-
774
- for (const file of outputs) {
775
- const fileDiv = document.createElement("div");
776
- fileDiv.className = "output-file";
777
-
778
- // Always show file header with download link
779
- const header = document.createElement("div");
780
- header.className = "output-file-header";
781
- const nameSpan = document.createElement("span");
782
- nameSpan.textContent = file.name;
783
- header.appendChild(nameSpan);
784
- const dlBtn = document.createElement("a");
785
- dlBtn.className = "dl-btn";
786
- dlBtn.textContent = "Download";
787
- dlBtn.download = file.name;
788
- dlBtn.href = getDownloadUri(file);
789
- header.appendChild(dlBtn);
790
- fileDiv.appendChild(header);
791
-
792
- const content = document.createElement("div");
793
- content.className = "output-file-content";
794
-
795
- if (file.type === "text") {
796
- const pre = document.createElement("pre");
797
- pre.textContent = file.content;
798
- content.appendChild(pre);
799
- } else if (file.type === "image") {
800
- const img = document.createElement("img");
801
- img.src = file.data_uri;
802
- img.alt = file.name;
803
- content.appendChild(img);
804
- } else if (file.type === "pdf") {
805
- const iframe = document.createElement("iframe");
806
- iframe.src = file.data_uri;
807
- content.appendChild(iframe);
808
- } else if (file.type === "xlsx") {
809
- renderXlsx(content, file.data_b64);
810
- } else if (file.type === "binary") {
811
- const a = document.createElement("a");
812
- a.className = "download-link";
813
- a.href = file.data_uri;
814
- a.download = file.name;
815
- a.textContent = "Download " + file.name;
816
- content.appendChild(a);
817
- } else if (file.type === "error") {
818
- const pre = document.createElement("pre");
819
- pre.textContent = file.content;
820
- pre.style.color = "var(--red)";
821
- content.appendChild(pre);
822
- }
823
-
824
- fileDiv.appendChild(content);
825
- container.appendChild(fileDiv);
826
- }
827
- }
828
-
829
- // ---- XLSX rendering via SheetJS ----
830
- function renderXlsx(container, b64Data) {
831
- try {
832
- const raw = Uint8Array.from(atob(b64Data), c => c.charCodeAt(0));
833
- const wb = XLSX.read(raw, { type: "array" });
834
-
835
- for (let i = 0; i < wb.SheetNames.length; i++) {
836
- const sheetName = wb.SheetNames[i];
837
- const ws = wb.Sheets[sheetName];
838
-
839
- if (wb.SheetNames.length > 1) {
840
- const sheetLabel = document.createElement("div");
841
- sheetLabel.style.cssText =
842
- "font-weight:600; font-size:0.8rem; color:#b0aea5; margin-top:0.5rem; margin-bottom:0.25rem;";
843
- sheetLabel.textContent = "Sheet: " + sheetName;
844
- container.appendChild(sheetLabel);
845
- }
846
-
847
- const htmlStr = XLSX.utils.sheet_to_html(ws, { editable: false });
848
- const wrapper = document.createElement("div");
849
- wrapper.innerHTML = htmlStr;
850
- container.appendChild(wrapper);
851
- }
852
- } catch (err) {
853
- container.textContent = "Error rendering spreadsheet: " + err.message;
854
- }
855
- }
856
-
857
- // ---- Grades ----
858
- function renderGrades(run) {
859
- const section = document.getElementById("grades-section");
860
- const content = document.getElementById("grades-content");
861
-
862
- if (!run.grading) {
863
- section.style.display = "none";
864
- return;
865
- }
866
-
867
- const grading = run.grading;
868
- section.style.display = "block";
869
- // Reset to collapsed
870
- content.classList.remove("open");
871
- document.getElementById("grades-arrow").classList.remove("open");
872
-
873
- const summary = grading.summary || {};
874
- const expectations = grading.expectations || [];
875
-
876
- let html = '<div style="padding: 1rem;">';
877
-
878
- // Summary line
879
- const passRate = summary.pass_rate != null
880
- ? Math.round(summary.pass_rate * 100) + "%"
881
- : "?";
882
- const badgeClass = summary.pass_rate >= 0.8 ? "grade-pass" : summary.pass_rate >= 0.5 ? "" : "grade-fail";
883
- html += '<div class="grades-summary">';
884
- html += '<span class="grade-badge ' + badgeClass + '">' + passRate + '</span>';
885
- html += '<span>' + (summary.passed || 0) + ' passed, ' + (summary.failed || 0) + ' failed of ' + (summary.total || 0) + '</span>';
886
- html += '</div>';
887
-
888
- // Assertions list
889
- html += '<ul class="assertion-list">';
890
- for (const exp of expectations) {
891
- const statusClass = exp.passed ? "pass" : "fail";
892
- const statusIcon = exp.passed ? "\u2713" : "\u2717";
893
- html += '<li class="assertion-item">';
894
- html += '<span class="assertion-status ' + statusClass + '">' + statusIcon + '</span>';
895
- html += '<span>' + escapeHtml(exp.text) + '</span>';
896
- if (exp.evidence) {
897
- html += '<div class="assertion-evidence">' + escapeHtml(exp.evidence) + '</div>';
898
- }
899
- html += '</li>';
900
- }
901
- html += '</ul>';
902
-
903
- html += '</div>';
904
- content.innerHTML = html;
905
- }
906
-
907
- function toggleGrades() {
908
- const content = document.getElementById("grades-content");
909
- const arrow = document.getElementById("grades-arrow");
910
- content.classList.toggle("open");
911
- arrow.classList.toggle("open");
912
- }
913
-
914
- // ---- Previous outputs (collapsible) ----
915
- function renderPrevOutputs(run) {
916
- const section = document.getElementById("prev-outputs-section");
917
- const content = document.getElementById("prev-outputs-content");
918
- const prevOutputs = (EMBEDDED_DATA.previous_outputs || {})[run.id];
919
-
920
- if (!prevOutputs || prevOutputs.length === 0) {
921
- section.style.display = "none";
922
- return;
923
- }
924
-
925
- section.style.display = "block";
926
- // Reset to collapsed
927
- content.classList.remove("open");
928
- document.getElementById("prev-outputs-arrow").classList.remove("open");
929
-
930
- // Render the files into the content area
931
- content.innerHTML = "";
932
- const wrapper = document.createElement("div");
933
- wrapper.style.padding = "1rem";
934
-
935
- for (const file of prevOutputs) {
936
- const fileDiv = document.createElement("div");
937
- fileDiv.className = "output-file";
938
-
939
- const header = document.createElement("div");
940
- header.className = "output-file-header";
941
- const nameSpan = document.createElement("span");
942
- nameSpan.textContent = file.name;
943
- header.appendChild(nameSpan);
944
- const dlBtn = document.createElement("a");
945
- dlBtn.className = "dl-btn";
946
- dlBtn.textContent = "Download";
947
- dlBtn.download = file.name;
948
- dlBtn.href = getDownloadUri(file);
949
- header.appendChild(dlBtn);
950
- fileDiv.appendChild(header);
951
-
952
- const fc = document.createElement("div");
953
- fc.className = "output-file-content";
954
-
955
- if (file.type === "text") {
956
- const pre = document.createElement("pre");
957
- pre.textContent = file.content;
958
- fc.appendChild(pre);
959
- } else if (file.type === "image") {
960
- const img = document.createElement("img");
961
- img.src = file.data_uri;
962
- img.alt = file.name;
963
- fc.appendChild(img);
964
- } else if (file.type === "pdf") {
965
- const iframe = document.createElement("iframe");
966
- iframe.src = file.data_uri;
967
- fc.appendChild(iframe);
968
- } else if (file.type === "xlsx") {
969
- renderXlsx(fc, file.data_b64);
970
- } else if (file.type === "binary") {
971
- const a = document.createElement("a");
972
- a.className = "download-link";
973
- a.href = file.data_uri;
974
- a.download = file.name;
975
- a.textContent = "Download " + file.name;
976
- fc.appendChild(a);
977
- }
978
-
979
- fileDiv.appendChild(fc);
980
- wrapper.appendChild(fileDiv);
981
- }
982
-
983
- content.appendChild(wrapper);
984
- }
985
-
986
- function togglePrevOutputs() {
987
- const content = document.getElementById("prev-outputs-content");
988
- const arrow = document.getElementById("prev-outputs-arrow");
989
- content.classList.toggle("open");
990
- arrow.classList.toggle("open");
991
- }
992
-
993
- // ---- Feedback (saved to server -> feedback.json) ----
994
- function saveCurrentFeedback() {
995
- const run = EMBEDDED_DATA.runs[currentIndex];
996
- const text = document.getElementById("feedback").value;
997
-
998
- if (text.trim() === "") {
999
- delete feedbackMap[run.id];
1000
- } else {
1001
- feedbackMap[run.id] = text;
1002
- }
1003
-
1004
- // Build reviews array from map
1005
- const reviews = [];
1006
- for (const [run_id, feedback] of Object.entries(feedbackMap)) {
1007
- if (feedback.trim()) {
1008
- reviews.push({ run_id, feedback, timestamp: new Date().toISOString() });
1009
- }
1010
- }
1011
-
1012
- fetch("/api/feedback", {
1013
- method: "POST",
1014
- headers: { "Content-Type": "application/json" },
1015
- body: JSON.stringify({ reviews, status: "in_progress" }),
1016
- }).then(() => {
1017
- document.getElementById("feedback-status").textContent = "Saved";
1018
- }).catch(() => {
1019
- // Static mode or server unavailable — no-op on auto-save,
1020
- // feedback will be downloaded on final submit
1021
- document.getElementById("feedback-status").textContent = "Will download on submit";
1022
- });
1023
- }
1024
-
1025
- // ---- Done ----
1026
- function showDoneDialog() {
1027
- // Save current textarea to feedbackMap (but don't POST yet)
1028
- const run = EMBEDDED_DATA.runs[currentIndex];
1029
- const text = document.getElementById("feedback").value;
1030
- if (text.trim() === "") {
1031
- delete feedbackMap[run.id];
1032
- } else {
1033
- feedbackMap[run.id] = text;
1034
- }
1035
-
1036
- // POST once with status: complete — include ALL runs so the model
1037
- // can distinguish "no feedback" (looks good) from "not reviewed"
1038
- const reviews = [];
1039
- const ts = new Date().toISOString();
1040
- for (const r of EMBEDDED_DATA.runs) {
1041
- reviews.push({ run_id: r.id, feedback: feedbackMap[r.id] || "", timestamp: ts });
1042
- }
1043
- const payload = JSON.stringify({ reviews, status: "complete" }, null, 2);
1044
- fetch("/api/feedback", {
1045
- method: "POST",
1046
- headers: { "Content-Type": "application/json" },
1047
- body: payload,
1048
- }).then(() => {
1049
- document.getElementById("done-overlay").classList.add("visible");
1050
- }).catch(() => {
1051
- // Server not available (static mode) — download as file
1052
- const blob = new Blob([payload], { type: "application/json" });
1053
- const url = URL.createObjectURL(blob);
1054
- const a = document.createElement("a");
1055
- a.href = url;
1056
- a.download = "feedback.json";
1057
- a.click();
1058
- URL.revokeObjectURL(url);
1059
- document.getElementById("done-overlay").classList.add("visible");
1060
- });
1061
- }
1062
-
1063
- function closeDoneDialog() {
1064
- // Reset status back to in_progress
1065
- saveCurrentFeedback();
1066
- document.getElementById("done-overlay").classList.remove("visible");
1067
- }
1068
-
1069
- // ---- Toast ----
1070
- function showToast(message) {
1071
- const toast = document.getElementById("toast");
1072
- toast.textContent = message;
1073
- toast.classList.add("visible");
1074
- setTimeout(() => toast.classList.remove("visible"), 2000);
1075
- }
1076
-
1077
- // ---- Keyboard nav ----
1078
- document.addEventListener("keydown", (e) => {
1079
- // Don't capture when typing in textarea
1080
- if (e.target.tagName === "TEXTAREA") return;
1081
-
1082
- if (e.key === "ArrowLeft" || e.key === "ArrowUp") {
1083
- e.preventDefault();
1084
- navigate(-1);
1085
- } else if (e.key === "ArrowRight" || e.key === "ArrowDown") {
1086
- e.preventDefault();
1087
- navigate(1);
1088
- }
1089
- });
1090
-
1091
- // ---- Util ----
1092
- function getDownloadUri(file) {
1093
- if (file.data_uri) return file.data_uri;
1094
- if (file.data_b64) return "data:application/octet-stream;base64," + file.data_b64;
1095
- if (file.type === "text") return "data:text/plain;charset=utf-8," + encodeURIComponent(file.content);
1096
- return "#";
1097
- }
1098
-
1099
- function escapeHtml(text) {
1100
- const div = document.createElement("div");
1101
- div.textContent = text;
1102
- return div.innerHTML;
1103
- }
1104
-
1105
- // ---- View switching ----
1106
- function switchView(view) {
1107
- document.querySelectorAll(".view-tab").forEach(t => t.classList.remove("active"));
1108
- document.querySelectorAll(".view-panel").forEach(p => p.classList.remove("active"));
1109
- document.querySelector(`[onclick="switchView('${view}')"]`).classList.add("active");
1110
- document.getElementById("panel-" + view).classList.add("active");
1111
- }
1112
-
1113
- // ---- Benchmark rendering ----
1114
- function renderBenchmark() {
1115
- const data = EMBEDDED_DATA.benchmark;
1116
- if (!data) return;
1117
-
1118
- // Show the tabs
1119
- document.getElementById("view-tabs").style.display = "flex";
1120
-
1121
- const container = document.getElementById("benchmark-content");
1122
- const summary = data.run_summary || {};
1123
- const metadata = data.metadata || {};
1124
- const notes = data.notes || [];
1125
-
1126
- let html = "";
1127
-
1128
- // Header
1129
- html += "<h2 style='font-family: Poppins, sans-serif; margin-bottom: 0.5rem;'>Benchmark Results</h2>";
1130
- html += "<p style='color: var(--text-muted); font-size: 0.875rem; margin-bottom: 1.25rem;'>";
1131
- if (metadata.skill_name) html += "<strong>" + escapeHtml(metadata.skill_name) + "</strong> &mdash; ";
1132
- if (metadata.timestamp) html += metadata.timestamp + " &mdash; ";
1133
- if (metadata.evals_run) html += "Evals: " + metadata.evals_run.join(", ") + " &mdash; ";
1134
- html += (metadata.runs_per_configuration || "?") + " runs per configuration";
1135
- html += "</p>";
1136
-
1137
- // Summary table
1138
- html += '<table class="benchmark-table">';
1139
-
1140
- function fmtStat(stat, pct) {
1141
- if (!stat) return "—";
1142
- const suffix = pct ? "%" : "";
1143
- const m = pct ? (stat.mean * 100).toFixed(0) : stat.mean.toFixed(1);
1144
- const s = pct ? (stat.stddev * 100).toFixed(0) : stat.stddev.toFixed(1);
1145
- return m + suffix + " ± " + s + suffix;
1146
- }
1147
-
1148
- function deltaClass(val) {
1149
- if (!val) return "";
1150
- const n = parseFloat(val);
1151
- if (n > 0) return "benchmark-delta-positive";
1152
- if (n < 0) return "benchmark-delta-negative";
1153
- return "";
1154
- }
1155
-
1156
- // Discover config names dynamically (everything except "delta")
1157
- const configs = Object.keys(summary).filter(k => k !== "delta");
1158
- const configA = configs[0] || "config_a";
1159
- const configB = configs[1] || "config_b";
1160
- const labelA = configA.replace(/_/g, " ").replace(/\b\w/g, c => c.toUpperCase());
1161
- const labelB = configB.replace(/_/g, " ").replace(/\b\w/g, c => c.toUpperCase());
1162
- const a = summary[configA] || {};
1163
- const b = summary[configB] || {};
1164
- const delta = summary.delta || {};
1165
-
1166
- html += "<thead><tr><th>Metric</th><th>" + escapeHtml(labelA) + "</th><th>" + escapeHtml(labelB) + "</th><th>Delta</th></tr></thead>";
1167
- html += "<tbody>";
1168
-
1169
- html += "<tr><td><strong>Pass Rate</strong></td>";
1170
- html += "<td>" + fmtStat(a.pass_rate, true) + "</td>";
1171
- html += "<td>" + fmtStat(b.pass_rate, true) + "</td>";
1172
- html += '<td class="' + deltaClass(delta.pass_rate) + '">' + (delta.pass_rate || "—") + "</td></tr>";
1173
-
1174
- // Time (only show row if data exists)
1175
- if (a.time_seconds || b.time_seconds) {
1176
- html += "<tr><td><strong>Time (s)</strong></td>";
1177
- html += "<td>" + fmtStat(a.time_seconds, false) + "</td>";
1178
- html += "<td>" + fmtStat(b.time_seconds, false) + "</td>";
1179
- html += '<td class="' + deltaClass(delta.time_seconds) + '">' + (delta.time_seconds ? delta.time_seconds + "s" : "—") + "</td></tr>";
1180
- }
1181
-
1182
- // Tokens (only show row if data exists)
1183
- if (a.tokens || b.tokens) {
1184
- html += "<tr><td><strong>Tokens</strong></td>";
1185
- html += "<td>" + fmtStat(a.tokens, false) + "</td>";
1186
- html += "<td>" + fmtStat(b.tokens, false) + "</td>";
1187
- html += '<td class="' + deltaClass(delta.tokens) + '">' + (delta.tokens || "—") + "</td></tr>";
1188
- }
1189
-
1190
- html += "</tbody></table>";
1191
-
1192
- // Per-eval breakdown (if runs data available)
1193
- const runs = data.runs || [];
1194
- if (runs.length > 0) {
1195
- const evalIds = [...new Set(runs.map(r => r.eval_id))].sort((a, b) => a - b);
1196
-
1197
- html += "<h3 style='font-family: Poppins, sans-serif; margin-bottom: 0.75rem;'>Per-Eval Breakdown</h3>";
1198
-
1199
- const hasTime = runs.some(r => r.result && r.result.time_seconds != null);
1200
- const hasErrors = runs.some(r => r.result && r.result.errors > 0);
1201
-
1202
- for (const evalId of evalIds) {
1203
- const evalRuns = runs.filter(r => r.eval_id === evalId);
1204
- const evalName = evalRuns[0] && evalRuns[0].eval_name ? evalRuns[0].eval_name : "Eval " + evalId;
1205
-
1206
- html += "<h4 style='font-family: Poppins, sans-serif; margin: 1rem 0 0.5rem; color: var(--text);'>" + escapeHtml(evalName) + "</h4>";
1207
- html += '<table class="benchmark-table">';
1208
- html += "<thead><tr><th>Config</th><th>Run</th><th>Pass Rate</th>";
1209
- if (hasTime) html += "<th>Time (s)</th>";
1210
- if (hasErrors) html += "<th>Crashes During Execution</th>";
1211
- html += "</tr></thead>";
1212
- html += "<tbody>";
1213
-
1214
- // Group by config and render with average rows
1215
- const configGroups = [...new Set(evalRuns.map(r => r.configuration))];
1216
- for (let ci = 0; ci < configGroups.length; ci++) {
1217
- const config = configGroups[ci];
1218
- const configRuns = evalRuns.filter(r => r.configuration === config);
1219
- if (configRuns.length === 0) continue;
1220
-
1221
- const rowClass = ci === 0 ? "benchmark-row-with" : "benchmark-row-without";
1222
- const configLabel = config.replace(/_/g, " ").replace(/\b\w/g, c => c.toUpperCase());
1223
-
1224
- for (const run of configRuns) {
1225
- const r = run.result || {};
1226
- const prClass = r.pass_rate >= 0.8 ? "benchmark-delta-positive" : r.pass_rate < 0.5 ? "benchmark-delta-negative" : "";
1227
- html += '<tr class="' + rowClass + '">';
1228
- html += "<td>" + configLabel + "</td>";
1229
- html += "<td>" + run.run_number + "</td>";
1230
- html += '<td class="' + prClass + '">' + ((r.pass_rate || 0) * 100).toFixed(0) + "% (" + (r.passed || 0) + "/" + (r.total || 0) + ")</td>";
1231
- if (hasTime) html += "<td>" + (r.time_seconds != null ? r.time_seconds.toFixed(1) : "—") + "</td>";
1232
- if (hasErrors) html += "<td>" + (r.errors || 0) + "</td>";
1233
- html += "</tr>";
1234
- }
1235
-
1236
- // Average row
1237
- const rates = configRuns.map(r => (r.result || {}).pass_rate || 0);
1238
- const avgRate = rates.reduce((a, b) => a + b, 0) / rates.length;
1239
- const avgPrClass = avgRate >= 0.8 ? "benchmark-delta-positive" : avgRate < 0.5 ? "benchmark-delta-negative" : "";
1240
- html += '<tr class="benchmark-row-avg ' + rowClass + '">';
1241
- html += "<td>" + configLabel + "</td>";
1242
- html += "<td>Avg</td>";
1243
- html += '<td class="' + avgPrClass + '">' + (avgRate * 100).toFixed(0) + "%</td>";
1244
- if (hasTime) {
1245
- const times = configRuns.map(r => (r.result || {}).time_seconds).filter(t => t != null);
1246
- html += "<td>" + (times.length ? (times.reduce((a, b) => a + b, 0) / times.length).toFixed(1) : "—") + "</td>";
1247
- }
1248
- if (hasErrors) html += "<td></td>";
1249
- html += "</tr>";
1250
- }
1251
- html += "</tbody></table>";
1252
-
1253
- // Per-assertion detail for this eval
1254
- const runsWithExpectations = {};
1255
- for (const config of configGroups) {
1256
- runsWithExpectations[config] = evalRuns.filter(r => r.configuration === config && r.expectations && r.expectations.length > 0);
1257
- }
1258
- const hasAnyExpectations = Object.values(runsWithExpectations).some(runs => runs.length > 0);
1259
- if (hasAnyExpectations) {
1260
- // Collect all unique assertion texts across all configs
1261
- const allAssertions = [];
1262
- const seen = new Set();
1263
- for (const config of configGroups) {
1264
- for (const run of runsWithExpectations[config]) {
1265
- for (const exp of (run.expectations || [])) {
1266
- if (!seen.has(exp.text)) {
1267
- seen.add(exp.text);
1268
- allAssertions.push(exp.text);
1269
- }
1270
- }
1271
- }
1272
- }
1273
-
1274
- html += '<table class="benchmark-table" style="margin-top: 0.5rem;">';
1275
- html += "<thead><tr><th>Assertion</th>";
1276
- for (const config of configGroups) {
1277
- const label = config.replace(/_/g, " ").replace(/\b\w/g, c => c.toUpperCase());
1278
- html += "<th>" + escapeHtml(label) + "</th>";
1279
- }
1280
- html += "</tr></thead><tbody>";
1281
-
1282
- for (const assertionText of allAssertions) {
1283
- html += "<tr><td>" + escapeHtml(assertionText) + "</td>";
1284
-
1285
- for (const config of configGroups) {
1286
- html += "<td>";
1287
- for (const run of runsWithExpectations[config]) {
1288
- const exp = (run.expectations || []).find(e => e.text === assertionText);
1289
- if (exp) {
1290
- const cls = exp.passed ? "benchmark-delta-positive" : "benchmark-delta-negative";
1291
- const icon = exp.passed ? "\u2713" : "\u2717";
1292
- html += '<span class="' + cls + '" title="Run ' + run.run_number + ': ' + escapeHtml(exp.evidence || "") + '">' + icon + "</span> ";
1293
- } else {
1294
- html += "— ";
1295
- }
1296
- }
1297
- html += "</td>";
1298
- }
1299
- html += "</tr>";
1300
- }
1301
- html += "</tbody></table>";
1302
- }
1303
- }
1304
- }
1305
-
1306
- // Notes
1307
- if (notes.length > 0) {
1308
- html += '<div class="benchmark-notes">';
1309
- html += "<h3>Analysis Notes</h3>";
1310
- html += "<ul>";
1311
- for (const note of notes) {
1312
- html += "<li>" + escapeHtml(note) + "</li>";
1313
- }
1314
- html += "</ul></div>";
1315
- }
1316
-
1317
- container.innerHTML = html;
1318
- }
1319
-
1320
- // ---- Start ----
1321
- init();
1322
- renderBenchmark();
1323
- </script>
1324
- </body>
1325
- </html>
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Eval Review</title>
7
+ <link rel="preconnect" href="https://fonts.googleapis.com">
8
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
9
+ <link href="https://fonts.googleapis.com/css2?family=Poppins:wght@500;600&family=Lora:wght@400;500&display=swap" rel="stylesheet">
10
+ <script src="https://cdn.sheetjs.com/xlsx-0.20.3/package/dist/xlsx.full.min.js" integrity="sha384-EnyY0/GSHQGSxSgMwaIPzSESbqoOLSexfnSMN2AP+39Ckmn92stwABZynq1JyzdT" crossorigin="anonymous"></script>
11
+ <style>
12
+ :root {
13
+ --bg: #faf9f5;
14
+ --surface: #ffffff;
15
+ --border: #e8e6dc;
16
+ --text: #141413;
17
+ --text-muted: #b0aea5;
18
+ --accent: #d97757;
19
+ --accent-hover: #c4613f;
20
+ --green: #788c5d;
21
+ --green-bg: #eef2e8;
22
+ --red: #c44;
23
+ --red-bg: #fceaea;
24
+ --header-bg: #141413;
25
+ --header-text: #faf9f5;
26
+ --radius: 6px;
27
+ }
28
+
29
+ * { box-sizing: border-box; margin: 0; padding: 0; }
30
+
31
+ body {
32
+ font-family: 'Lora', Georgia, serif;
33
+ background: var(--bg);
34
+ color: var(--text);
35
+ height: 100vh;
36
+ display: flex;
37
+ flex-direction: column;
38
+ }
39
+
40
+ /* ---- Header ---- */
41
+ .header {
42
+ background: var(--header-bg);
43
+ color: var(--header-text);
44
+ padding: 1rem 2rem;
45
+ display: flex;
46
+ justify-content: space-between;
47
+ align-items: center;
48
+ flex-shrink: 0;
49
+ }
50
+ .header h1 {
51
+ font-family: 'Poppins', sans-serif;
52
+ font-size: 1.25rem;
53
+ font-weight: 600;
54
+ }
55
+ .header .instructions {
56
+ font-size: 0.8rem;
57
+ opacity: 0.7;
58
+ margin-top: 0.25rem;
59
+ }
60
+ .header .progress {
61
+ font-size: 0.875rem;
62
+ opacity: 0.8;
63
+ text-align: right;
64
+ }
65
+
66
+ /* ---- Main content ---- */
67
+ .main {
68
+ flex: 1;
69
+ overflow-y: auto;
70
+ padding: 1.5rem 2rem;
71
+ display: flex;
72
+ flex-direction: column;
73
+ gap: 1.25rem;
74
+ }
75
+
76
+ /* ---- Sections ---- */
77
+ .section {
78
+ background: var(--surface);
79
+ border: 1px solid var(--border);
80
+ border-radius: var(--radius);
81
+ flex-shrink: 0;
82
+ }
83
+ .section-header {
84
+ font-family: 'Poppins', sans-serif;
85
+ padding: 0.75rem 1rem;
86
+ font-size: 0.75rem;
87
+ font-weight: 500;
88
+ text-transform: uppercase;
89
+ letter-spacing: 0.05em;
90
+ color: var(--text-muted);
91
+ border-bottom: 1px solid var(--border);
92
+ background: var(--bg);
93
+ }
94
+ .section-body {
95
+ padding: 1rem;
96
+ }
97
+
98
+ /* ---- Config badge ---- */
99
+ .config-badge {
100
+ display: inline-block;
101
+ padding: 0.2rem 0.625rem;
102
+ border-radius: 9999px;
103
+ font-family: 'Poppins', sans-serif;
104
+ font-size: 0.6875rem;
105
+ font-weight: 600;
106
+ text-transform: uppercase;
107
+ letter-spacing: 0.03em;
108
+ margin-left: 0.75rem;
109
+ vertical-align: middle;
110
+ }
111
+ .config-badge.config-primary {
112
+ background: rgba(33, 150, 243, 0.12);
113
+ color: #1976d2;
114
+ }
115
+ .config-badge.config-baseline {
116
+ background: rgba(255, 193, 7, 0.15);
117
+ color: #f57f17;
118
+ }
119
+
120
+ /* ---- Prompt ---- */
121
+ .prompt-text {
122
+ white-space: pre-wrap;
123
+ font-size: 0.9375rem;
124
+ line-height: 1.6;
125
+ }
126
+
127
+ /* ---- Outputs ---- */
128
+ .output-file {
129
+ border: 1px solid var(--border);
130
+ border-radius: var(--radius);
131
+ overflow: hidden;
132
+ }
133
+ .output-file + .output-file {
134
+ margin-top: 1rem;
135
+ }
136
+ .output-file-header {
137
+ padding: 0.5rem 0.75rem;
138
+ font-size: 0.8rem;
139
+ font-weight: 600;
140
+ color: var(--text-muted);
141
+ background: var(--bg);
142
+ border-bottom: 1px solid var(--border);
143
+ font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace;
144
+ display: flex;
145
+ justify-content: space-between;
146
+ align-items: center;
147
+ }
148
+ .output-file-header .dl-btn {
149
+ font-size: 0.7rem;
150
+ color: var(--accent);
151
+ text-decoration: none;
152
+ cursor: pointer;
153
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
154
+ font-weight: 500;
155
+ opacity: 0.8;
156
+ }
157
+ .output-file-header .dl-btn:hover {
158
+ opacity: 1;
159
+ text-decoration: underline;
160
+ }
161
+ .output-file-content {
162
+ padding: 0.75rem;
163
+ overflow-x: auto;
164
+ }
165
+ .output-file-content pre {
166
+ font-size: 0.8125rem;
167
+ line-height: 1.5;
168
+ white-space: pre-wrap;
169
+ word-break: break-word;
170
+ font-family: 'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace;
171
+ }
172
+ .output-file-content img {
173
+ max-width: 100%;
174
+ height: auto;
175
+ border-radius: 4px;
176
+ }
177
+ .output-file-content iframe {
178
+ width: 100%;
179
+ height: 600px;
180
+ border: none;
181
+ }
182
+ .output-file-content table {
183
+ border-collapse: collapse;
184
+ font-size: 0.8125rem;
185
+ width: 100%;
186
+ }
187
+ .output-file-content table td,
188
+ .output-file-content table th {
189
+ border: 1px solid var(--border);
190
+ padding: 0.375rem 0.5rem;
191
+ text-align: left;
192
+ }
193
+ .output-file-content table th {
194
+ background: var(--bg);
195
+ font-weight: 600;
196
+ }
197
+ .output-file-content .download-link {
198
+ display: inline-flex;
199
+ align-items: center;
200
+ gap: 0.5rem;
201
+ padding: 0.5rem 1rem;
202
+ background: var(--bg);
203
+ border: 1px solid var(--border);
204
+ border-radius: 4px;
205
+ color: var(--accent);
206
+ text-decoration: none;
207
+ font-size: 0.875rem;
208
+ cursor: pointer;
209
+ }
210
+ .output-file-content .download-link:hover {
211
+ background: var(--border);
212
+ }
213
+ .empty-state {
214
+ color: var(--text-muted);
215
+ font-style: italic;
216
+ padding: 2rem;
217
+ text-align: center;
218
+ }
219
+
220
+ /* ---- Feedback ---- */
221
+ .prev-feedback {
222
+ background: var(--bg);
223
+ border: 1px solid var(--border);
224
+ border-radius: 4px;
225
+ padding: 0.625rem 0.75rem;
226
+ margin-top: 0.75rem;
227
+ font-size: 0.8125rem;
228
+ color: var(--text-muted);
229
+ line-height: 1.5;
230
+ }
231
+ .prev-feedback-label {
232
+ font-size: 0.7rem;
233
+ font-weight: 600;
234
+ text-transform: uppercase;
235
+ letter-spacing: 0.04em;
236
+ margin-bottom: 0.25rem;
237
+ color: var(--text-muted);
238
+ }
239
+ .feedback-textarea {
240
+ width: 100%;
241
+ min-height: 100px;
242
+ padding: 0.75rem;
243
+ border: 1px solid var(--border);
244
+ border-radius: 4px;
245
+ font-family: inherit;
246
+ font-size: 0.9375rem;
247
+ line-height: 1.5;
248
+ resize: vertical;
249
+ color: var(--text);
250
+ }
251
+ .feedback-textarea:focus {
252
+ outline: none;
253
+ border-color: var(--accent);
254
+ box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.1);
255
+ }
256
+ .feedback-status {
257
+ font-size: 0.75rem;
258
+ color: var(--text-muted);
259
+ margin-top: 0.5rem;
260
+ min-height: 1.1em;
261
+ }
262
+
263
+ /* ---- Grades (collapsible) ---- */
264
+ .grades-toggle {
265
+ display: flex;
266
+ align-items: center;
267
+ cursor: pointer;
268
+ user-select: none;
269
+ }
270
+ .grades-toggle:hover {
271
+ color: var(--accent);
272
+ }
273
+ .grades-toggle .arrow {
274
+ margin-right: 0.5rem;
275
+ transition: transform 0.15s;
276
+ font-size: 0.75rem;
277
+ }
278
+ .grades-toggle .arrow.open {
279
+ transform: rotate(90deg);
280
+ }
281
+ .grades-content {
282
+ display: none;
283
+ margin-top: 0.75rem;
284
+ }
285
+ .grades-content.open {
286
+ display: block;
287
+ }
288
+ .grades-summary {
289
+ font-size: 0.875rem;
290
+ margin-bottom: 0.75rem;
291
+ display: flex;
292
+ align-items: center;
293
+ gap: 0.5rem;
294
+ }
295
+ .grade-badge {
296
+ display: inline-block;
297
+ padding: 0.125rem 0.5rem;
298
+ border-radius: 9999px;
299
+ font-size: 0.75rem;
300
+ font-weight: 600;
301
+ }
302
+ .grade-pass { background: var(--green-bg); color: var(--green); }
303
+ .grade-fail { background: var(--red-bg); color: var(--red); }
304
+ .assertion-list {
305
+ list-style: none;
306
+ }
307
+ .assertion-item {
308
+ padding: 0.625rem 0;
309
+ border-bottom: 1px solid var(--border);
310
+ font-size: 0.8125rem;
311
+ }
312
+ .assertion-item:last-child { border-bottom: none; }
313
+ .assertion-status {
314
+ font-weight: 600;
315
+ margin-right: 0.5rem;
316
+ }
317
+ .assertion-status.pass { color: var(--green); }
318
+ .assertion-status.fail { color: var(--red); }
319
+ .assertion-evidence {
320
+ color: var(--text-muted);
321
+ font-size: 0.75rem;
322
+ margin-top: 0.25rem;
323
+ padding-left: 1.5rem;
324
+ }
325
+
326
+ /* ---- View tabs ---- */
327
+ .view-tabs {
328
+ display: flex;
329
+ gap: 0;
330
+ padding: 0 2rem;
331
+ background: var(--bg);
332
+ border-bottom: 1px solid var(--border);
333
+ flex-shrink: 0;
334
+ }
335
+ .view-tab {
336
+ font-family: 'Poppins', sans-serif;
337
+ padding: 0.625rem 1.25rem;
338
+ font-size: 0.8125rem;
339
+ font-weight: 500;
340
+ cursor: pointer;
341
+ border: none;
342
+ background: none;
343
+ color: var(--text-muted);
344
+ border-bottom: 2px solid transparent;
345
+ transition: all 0.15s;
346
+ }
347
+ .view-tab:hover { color: var(--text); }
348
+ .view-tab.active {
349
+ color: var(--accent);
350
+ border-bottom-color: var(--accent);
351
+ }
352
+ .view-panel { display: none; }
353
+ .view-panel.active { display: flex; flex-direction: column; flex: 1; overflow: hidden; }
354
+
355
+ /* ---- Benchmark view ---- */
356
+ .benchmark-view {
357
+ padding: 1.5rem 2rem;
358
+ overflow-y: auto;
359
+ flex: 1;
360
+ }
361
+ .benchmark-table {
362
+ border-collapse: collapse;
363
+ background: var(--surface);
364
+ border: 1px solid var(--border);
365
+ border-radius: var(--radius);
366
+ font-size: 0.8125rem;
367
+ width: 100%;
368
+ margin-bottom: 1.5rem;
369
+ }
370
+ .benchmark-table th, .benchmark-table td {
371
+ padding: 0.625rem 0.75rem;
372
+ text-align: left;
373
+ border: 1px solid var(--border);
374
+ }
375
+ .benchmark-table th {
376
+ font-family: 'Poppins', sans-serif;
377
+ background: var(--header-bg);
378
+ color: var(--header-text);
379
+ font-weight: 500;
380
+ font-size: 0.75rem;
381
+ text-transform: uppercase;
382
+ letter-spacing: 0.04em;
383
+ }
384
+ .benchmark-table tr:hover { background: var(--bg); }
385
+ .benchmark-table tr.benchmark-row-with { background: rgba(33, 150, 243, 0.06); }
386
+ .benchmark-table tr.benchmark-row-without { background: rgba(255, 193, 7, 0.06); }
387
+ .benchmark-table tr.benchmark-row-with:hover { background: rgba(33, 150, 243, 0.12); }
388
+ .benchmark-table tr.benchmark-row-without:hover { background: rgba(255, 193, 7, 0.12); }
389
+ .benchmark-table tr.benchmark-row-avg { font-weight: 600; border-top: 2px solid var(--border); }
390
+ .benchmark-table tr.benchmark-row-avg.benchmark-row-with { background: rgba(33, 150, 243, 0.12); }
391
+ .benchmark-table tr.benchmark-row-avg.benchmark-row-without { background: rgba(255, 193, 7, 0.12); }
392
+ .benchmark-delta-positive { color: var(--green); font-weight: 600; }
393
+ .benchmark-delta-negative { color: var(--red); font-weight: 600; }
394
+ .benchmark-notes {
395
+ background: var(--surface);
396
+ border: 1px solid var(--border);
397
+ border-radius: var(--radius);
398
+ padding: 1rem;
399
+ }
400
+ .benchmark-notes h3 {
401
+ font-family: 'Poppins', sans-serif;
402
+ font-size: 0.875rem;
403
+ margin-bottom: 0.75rem;
404
+ }
405
+ .benchmark-notes ul {
406
+ list-style: disc;
407
+ padding-left: 1.25rem;
408
+ }
409
+ .benchmark-notes li {
410
+ font-size: 0.8125rem;
411
+ line-height: 1.6;
412
+ margin-bottom: 0.375rem;
413
+ }
414
+ .benchmark-empty {
415
+ color: var(--text-muted);
416
+ font-style: italic;
417
+ text-align: center;
418
+ padding: 3rem;
419
+ }
420
+
421
+ /* ---- Navigation ---- */
422
+ .nav {
423
+ display: flex;
424
+ justify-content: space-between;
425
+ align-items: center;
426
+ padding: 1rem 2rem;
427
+ border-top: 1px solid var(--border);
428
+ background: var(--surface);
429
+ flex-shrink: 0;
430
+ }
431
+ .nav-btn {
432
+ font-family: 'Poppins', sans-serif;
433
+ padding: 0.5rem 1.25rem;
434
+ border: 1px solid var(--border);
435
+ border-radius: var(--radius);
436
+ background: var(--surface);
437
+ cursor: pointer;
438
+ font-size: 0.875rem;
439
+ font-weight: 500;
440
+ color: var(--text);
441
+ transition: all 0.15s;
442
+ }
443
+ .nav-btn:hover:not(:disabled) {
444
+ background: var(--bg);
445
+ border-color: var(--text-muted);
446
+ }
447
+ .nav-btn:disabled {
448
+ opacity: 0.4;
449
+ cursor: not-allowed;
450
+ }
451
+ .done-btn {
452
+ font-family: 'Poppins', sans-serif;
453
+ padding: 0.5rem 1.5rem;
454
+ border: 1px solid var(--border);
455
+ border-radius: var(--radius);
456
+ background: var(--surface);
457
+ color: var(--text);
458
+ cursor: pointer;
459
+ font-size: 0.875rem;
460
+ font-weight: 500;
461
+ transition: all 0.15s;
462
+ }
463
+ .done-btn:hover {
464
+ background: var(--bg);
465
+ border-color: var(--text-muted);
466
+ }
467
+ .done-btn.ready {
468
+ border: none;
469
+ background: var(--accent);
470
+ color: white;
471
+ font-weight: 600;
472
+ }
473
+ .done-btn.ready:hover {
474
+ background: var(--accent-hover);
475
+ }
476
+ /* ---- Done overlay ---- */
477
+ .done-overlay {
478
+ display: none;
479
+ position: fixed;
480
+ inset: 0;
481
+ background: rgba(0, 0, 0, 0.5);
482
+ z-index: 100;
483
+ justify-content: center;
484
+ align-items: center;
485
+ }
486
+ .done-overlay.visible {
487
+ display: flex;
488
+ }
489
+ .done-card {
490
+ background: var(--surface);
491
+ border-radius: 12px;
492
+ padding: 2rem 3rem;
493
+ text-align: center;
494
+ box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
495
+ max-width: 500px;
496
+ }
497
+ .done-card h2 {
498
+ font-size: 1.5rem;
499
+ margin-bottom: 0.5rem;
500
+ }
501
+ .done-card p {
502
+ color: var(--text-muted);
503
+ margin-bottom: 1.5rem;
504
+ line-height: 1.5;
505
+ }
506
+ .done-card .btn-row {
507
+ display: flex;
508
+ gap: 0.5rem;
509
+ justify-content: center;
510
+ }
511
+ .done-card button {
512
+ padding: 0.5rem 1.25rem;
513
+ border: 1px solid var(--border);
514
+ border-radius: var(--radius);
515
+ background: var(--surface);
516
+ cursor: pointer;
517
+ font-size: 0.875rem;
518
+ }
519
+ .done-card button:hover {
520
+ background: var(--bg);
521
+ }
522
+ /* ---- Toast ---- */
523
+ .toast {
524
+ position: fixed;
525
+ bottom: 5rem;
526
+ left: 50%;
527
+ transform: translateX(-50%);
528
+ background: var(--header-bg);
529
+ color: var(--header-text);
530
+ padding: 0.625rem 1.25rem;
531
+ border-radius: var(--radius);
532
+ font-size: 0.875rem;
533
+ opacity: 0;
534
+ transition: opacity 0.3s;
535
+ pointer-events: none;
536
+ z-index: 200;
537
+ }
538
+ .toast.visible {
539
+ opacity: 1;
540
+ }
541
+ </style>
542
+ </head>
543
+ <body>
544
+ <div id="app" style="height:100vh; display:flex; flex-direction:column;">
545
+ <div class="header">
546
+ <div>
547
+ <h1>Eval Review: <span id="skill-name"></span></h1>
548
+ <div class="instructions">Review each output and leave feedback below. Navigate with arrow keys or buttons. When done, copy feedback and paste into Claude Code.</div>
549
+ </div>
550
+ <div class="progress" id="progress"></div>
551
+ </div>
552
+
553
+ <!-- View tabs (only shown when benchmark data exists) -->
554
+ <div class="view-tabs" id="view-tabs" style="display:none;">
555
+ <button class="view-tab active" onclick="switchView('outputs')">Outputs</button>
556
+ <button class="view-tab" onclick="switchView('benchmark')">Benchmark</button>
557
+ </div>
558
+
559
+ <!-- Outputs panel (qualitative review) -->
560
+ <div class="view-panel active" id="panel-outputs">
561
+ <div class="main">
562
+ <!-- Prompt -->
563
+ <div class="section">
564
+ <div class="section-header">Prompt <span class="config-badge" id="config-badge" style="display:none;"></span></div>
565
+ <div class="section-body">
566
+ <div class="prompt-text" id="prompt-text"></div>
567
+ </div>
568
+ </div>
569
+
570
+ <!-- Outputs -->
571
+ <div class="section">
572
+ <div class="section-header">Output</div>
573
+ <div class="section-body" id="outputs-body">
574
+ <div class="empty-state">No output files found</div>
575
+ </div>
576
+ </div>
577
+
578
+ <!-- Previous Output (collapsible) -->
579
+ <div class="section" id="prev-outputs-section" style="display:none;">
580
+ <div class="section-header">
581
+ <div class="grades-toggle" onclick="togglePrevOutputs()">
582
+ <span class="arrow" id="prev-outputs-arrow">&#9654;</span>
583
+ Previous Output
584
+ </div>
585
+ </div>
586
+ <div class="grades-content" id="prev-outputs-content"></div>
587
+ </div>
588
+
589
+ <!-- Grades (collapsible) -->
590
+ <div class="section" id="grades-section" style="display:none;">
591
+ <div class="section-header">
592
+ <div class="grades-toggle" onclick="toggleGrades()">
593
+ <span class="arrow" id="grades-arrow">&#9654;</span>
594
+ Formal Grades
595
+ </div>
596
+ </div>
597
+ <div class="grades-content" id="grades-content"></div>
598
+ </div>
599
+
600
+ <!-- Feedback -->
601
+ <div class="section">
602
+ <div class="section-header">Your Feedback</div>
603
+ <div class="section-body">
604
+ <textarea
605
+ class="feedback-textarea"
606
+ id="feedback"
607
+ placeholder="What do you think of this output? Any issues, suggestions, or things that look great?"
608
+ ></textarea>
609
+ <div class="feedback-status" id="feedback-status"></div>
610
+ <div class="prev-feedback" id="prev-feedback" style="display:none;">
611
+ <div class="prev-feedback-label">Previous feedback</div>
612
+ <div id="prev-feedback-text"></div>
613
+ </div>
614
+ </div>
615
+ </div>
616
+ </div>
617
+
618
+ <div class="nav" id="outputs-nav">
619
+ <button class="nav-btn" id="prev-btn" onclick="navigate(-1)">&#8592; Previous</button>
620
+ <button class="done-btn" id="done-btn" onclick="showDoneDialog()">Submit All Reviews</button>
621
+ <button class="nav-btn" id="next-btn" onclick="navigate(1)">Next &#8594;</button>
622
+ </div>
623
+ </div><!-- end panel-outputs -->
624
+
625
+ <!-- Benchmark panel (quantitative stats) -->
626
+ <div class="view-panel" id="panel-benchmark">
627
+ <div class="benchmark-view" id="benchmark-content">
628
+ <div class="benchmark-empty">No benchmark data available. Run a benchmark to see quantitative results here.</div>
629
+ </div>
630
+ </div>
631
+ </div>
632
+
633
+ <!-- Done overlay -->
634
+ <div class="done-overlay" id="done-overlay">
635
+ <div class="done-card">
636
+ <h2>Review Complete</h2>
637
+ <p>Your feedback has been saved. Go back to your Claude Code session and tell Claude you're done reviewing.</p>
638
+ <div class="btn-row">
639
+ <button onclick="closeDoneDialog()">OK</button>
640
+ </div>
641
+ </div>
642
+ </div>
643
+
644
+ <!-- Toast -->
645
+ <div class="toast" id="toast"></div>
646
+
647
+ <script>
648
+ // ---- Embedded data (injected by generate_review.py) ----
649
+ const EMBEDDED_DATA = {"skill_name": "sdd-task-plan-expert", "runs": [{"id": "eval-1-happy-path-with_skill", "prompt": "Gere o TASK PLAN para a feature de usuario. O SPEC_TECH aprovado esta em docs/feature-user/v1/spec_tech.md", "eval_id": 1, "outputs": [{"name": "response.md", "type": "text", "content": "# Resposta ao Usuario \u00e2\u20ac\u201d Primeiro Turno de Interacao\n\n---\n\nObrigado! Li o SPEC_TECH aprovado em `docs/feature-user/v1/spec_tech.md`.\n\nExtraindo o nome da feature da secao \"1. Visao Geral Tecnica\":\n\n> **Feature**: Modulo de Usuario -- Vakinha Burger\n\nNome normalizado para o framework SDD (kebab-case): **`feature-user`**\n\nDiretorio de trabalho: `docs/feature-user/v1/` (ja existente, onde vivem o `prd.md` e o `spec_tech.md`)\n\n---\n\n## Analise do Codebase Existente\n\nAntes de propor as fases, analisei o projeto e identifiquei o estado atual:\n\n### O que ja existe (e sera MODIFICADO)\n| Camada | Arquivo | Estado Atual |\n|--------|---------|-------------|\n| Proto | `api/proto/v1/user.proto` | `CreateUser` + `GetUser` (sera substituido por `Login`, `GetUserLogged`, `UpdateUser`) |\n| Repository | `internal/repository/user_repository.go` | `Create` + `GetByID`; modelo `User` sem `Address`; interface `UserRepository` sem `GetByEmail`/`Update` |\n| Service | `internal/service/user_service.go` | `CreateUser` + `GetUser`; sem `Login`, `GetUserLogged`, `UpdateUser`; sem logger/config; validacoes basicas |\n| Service Tests | `internal/service/user_service_test.go` | Testes existentes para `CreateUser` e `GetUser` |\n| Handler | `internal/handler/grpc/user_handler.go` | `CreateUser` + `GetUser`; `mapServiceError` basico |\n| Handler Tests | `internal/handler/grpc/user_handler_test.go` | Testes existentes para os handlers atuais |\n| Interceptor | `internal/infra/grpc/interceptors/auth.go` | JWT parse manual; `skipMethods` apenas `CreateUser`; nao injeta `user_id` no contexto |\n| Interceptor Tests | `internal/infra/grpc/interceptors/auth_test.go` | Testes existentes |\n| Config | `internal/infra/config/config.go` | Sem campo `JWTExpirationHours` |\n| Config YAML | `configs/config.yaml` | Sem `jwt.expiration_hours` |\n| Queries SQLC | `internal/db/sqlc/queries/user.sql` | Apenas `CreateUser` e `GetUserByID` |\n| Migration | `internal/db/migrations/001_create_usuarios.sql` | Tabela `usuarios` sem coluna `endereco` |\n\n### O que NAO existe (sera CRIADO)\n| Camada | Arquivo | Descricao |\n|--------|---------|-----------|\n| Identity | `internal/pkg/identity/identity.go` | Pacote JWT: Claims, GenerateToken, ParseToken, WithUserID, UserIDFromContext |\n| Identity Tests | `internal/pkg/identity/identity_test.go` | Testes unitarios do pacote identity |\n| Migration | `internal/db/migrations/002_add_endereco_usuarios.sql` | ADD COLUMN `endereco` |\n| Integration Tests | `internal/repository/user_repository_integration_test.go` | Testes de integracao com SQLite real |\n| E2E Tests | `internal/e2e/user_e2e_test.go` | Testes E2E com servidor gRPC real |\n\n---\n\n## Proposta de Macro-Fases\n\nCom base no SPEC_TECH, nas dependencias entre camadas e no principio de \"cada fase entrega valor incremental e testavel\", proponho as seguintes macro-fases:\n\n### Fase 1 -- Fundacao: Configuracao, Migration e Identity\n**Objetivo:** Preparar a infraestrutura necessaria para as demais fases. Criar o pacote `identity` (JWT + contexto), adicionar a coluna `endereco` ao banco, e atualizar a configuracao com `JWTExpirationHours`.\n\n**Justificativa:** Tudo que vem depois depende do pacote identity e da coluna `endereco` existirem. Sao pecas fundamentais sem dependencia entre si (podem ser paralelizadas).\n\n**Tasks previstas:**\n- T1: Configuracao (`config.go` + `config.yaml` \u00e2\u20ac\u201d adicionar `JWTExpirationHours`)\n- T2: Migration `002_add_endereco_usuarios.sql`\n- T3: Pacote `internal/pkg/identity` (criar completo com testes)\n\n### Fase 2 -- Contratos e Dados: Proto, Queries SQLC e Repository\n**Objetivo:** Atualizar os contratos gRPC (proto), as queries SQLC e a camada repository para suportar todos os 4 endpoints e o campo `address`.\n\n**Justificativa:** Service e Handler dependem dos contratos proto e da interface do repository estarem prontos. Apos esta fase, `make generate` pode ser executado e o repositorio esta completo.\n\n**Tasks previstas:**\n- T4: Atualizar `user.proto` (substituicao completa conforme SPEC)\n- T5: Atualizar queries SQLC (`user.sql`) + executar `make generate`\n- T6: Atualizar `user_repository.go` (modelo `User` com `Address`, novos metodos `GetByEmail` e `Update`)\n\n### Fase 3 -- Logica de Negocio: Service\n**Objetivo:** Implementar toda a logica de negocio no service: validacoes, `Login`, `GetUserLogged`, `UpdateUser`, integracao com identity e logger.\n\n**Justificativa:** O service depende do repository (Fase 2) e do identity (Fase 1) estarem prontos. E a camada mais complexa, com todas as regras de negocio (RN-1 a RN-12).\n\n**Tasks previstas:**\n- T7: Atualizar `user_service.go` (novos metodos, validacoes, logger, config)\n\n### Fase 4 -- Apresentacao: Handler e Interceptor\n**Objetivo:** Atualizar os handlers gRPC e o interceptor de autenticacao para expor os 4 endpoints, mapear erros corretamente e injetar `user_id` no contexto.\n\n**Justificativa:** Depende do service (Fase 3) e do identity (Fase 1). Completa a stack funcional.\n\n**Tasks previstas:**\n- T8: Atualizar interceptor `auth.go` (adicionar `Login` em skipMethods, usar `identity.ParseToken`, injetar `user_id`)\n- T9: Atualizar `user_handler.go` (remover `GetUser`, adicionar `Login`, `GetUserLogged`, `UpdateUser`, atualizar `mapServiceError`)\n\n### Fase 5 -- Testes\n**Objetivo:** Criar/atualizar todos os testes unitarios, de integracao e E2E conforme definido na secao 14 do SPEC_TECH.\n\n**Justificativa:** Testes dependem de toda a implementacao estar pronta. Executar ao final garante cobertura completa e nao exige retrabalho.\n\n**Tasks previstas:**\n- T10: Atualizar testes unitarios do service (`user_service_test.go`)\n- T11: Atualizar testes unitarios do handler (`user_handler_test.go`)\n- T12: Atualizar testes do interceptor (`auth_test.go`)\n- T13: Criar testes de integracao do repository (`user_repository_integration_test.go`)\n- T14: Criar testes E2E (`user_e2e_test.go`)\n\n---\n\n## Rastreabilidade Preliminar (User Stories -> Fases)\n\n| User Story | Descricao | Fases Envolvidas |\n|------------|-----------|-----------------|\n| US-01 | Cadastro de novo usuario | F1 (config, migration, identity), F2 (proto, queries, repo), F3 (service), F4 (handler), F5 (testes) |\n| US-02 | Login com email e senha | F1 (config, identity), F2 (proto, repo), F3 (service), F4 (handler, interceptor), F5 (testes) |\n| US-03 | Consultar dados do usuario autenticado | F1 (identity), F2 (proto, repo), F3 (service), F4 (handler, interceptor), F5 (testes) |\n| US-04 | Atualizar dados cadastrais | F1 (identity, migration), F2 (proto, queries, repo), F3 (service), F4 (handler, interceptor), F5 (testes) |\n\n---\n\n## Paralelismo Identificado\n\n- **Fase 1:** T1, T2 e T3 podem rodar em paralelo (sem dependencia entre si)\n- **Fase 2:** T4 e T5 sao sequenciais (proto antes de queries/generate), T6 depende de T5\n- **Fase 5:** T10, T11, T12 podem rodar em paralelo; T13 e T14 podem rodar em paralelo entre si\n\n---\n\nPodemos iniciar a definicao macro das fases? Voce concorda com esta estrutura de 5 fases, ou gostaria de ajustar algo (unir/separar fases, mudar ordem, realocar tasks)?\n\n---\n\n*[Simulacao de resposta do usuario para fins de avaliacao]*\n\n**Pergunta que seria feita:** \"Podemos iniciar a definicao macro das fases? Voce concorda com esta estrutura de 5 fases, ou gostaria de ajustar algo?\"\n\n**Resposta simulada:** \"Sim, a estrutura de 5 fases esta otima. Podemos prosseguir com o detalhamento das tasks da Fase 1.\"\n\n---\n\n> **Nota:** Este e o primeiro turno de interacao. Conforme as instrucoes da avaliacao, o processo foi interrompido aqui. O proximo passo seria destrinchar as tasks da Fase 1 individualmente, criando os arquivos `tasks/T1.md`, `tasks/T2.md` e `tasks/T3.md` com o template completo (secoes 1-5 e 7-8 pelo engenheiro de tarefas, secao 6 delegada ao subagente QA), salvando cada arquivo antes de apresentar ao usuario para aprovacao.\n"}], "grading": {"eval_id": 1, "eval_name": "happy-path-spec-tech-usuario", "configuration": "with_skill", "expectations": [{"text": "Extrai o nome da feature do SPEC_TECH e confirma com o usuario antes de prosseguir", "passed": true, "evidence": "Extraiu 'Modulo de Usuario -- Vakinha Burger', normalizou para 'feature-user' em kebab-case, e perguntou: 'Podemos iniciar a definicao macro das fases? Voce concorda com esta estrutura de 5 fases?'"}, {"text": "Faz apenas UMA pergunta por vez (nao bombardeia com multiplas perguntas no mesmo turno)", "passed": true, "evidence": "Fez apenas uma pergunta ao final: 'Podemos iniciar a definicao macro das fases?' Nao bombardeou com multiplas perguntas."}, {"text": "Propoe macro-fases de alto nivel e aguarda validacao antes de criar tasks", "passed": true, "evidence": "Propos 5 fases (Fundacao, Contratos/Dados, Logica de Negocio, Apresentacao, Testes) com justificativa para cada uma, e aguardou validacao antes de criar tasks detalhadas."}, {"text": "Le as rules do projeto (.claude/rules/ e CLAUDE.md) ANTES de gerar tasks", "passed": false, "evidence": "O transcript mostra leitura extensiva do codebase (13 arquivos Go, proto, config, PRD), mas NAO ha leitura explicita de .claude/rules/ nem CLAUDE.md. O agente explorou o codebase mas nao leu as rules do projeto."}, {"text": "NAO inicia execucao automatica de tasks \u00e2\u20ac\u201d apenas apresenta o plano e aguarda", "passed": true, "evidence": "Encerrou com 'Este e o primeiro turno de interacao' e explicou que o proximo passo seria destrinchar tasks da Fase 1. Nao iniciou execucao automatica."}]}}, {"id": "eval-1-happy-path-without_skill", "prompt": "Gere o TASK PLAN para a feature de usuario. O SPEC_TECH aprovado esta em docs/feature-user/v1/spec_tech.md", "eval_id": 1, "outputs": [{"name": "response.md", "type": "text", "content": "# TASK PLAN -- Modulo de Usuario v1\n\n**Baseado em:** `docs/feature-user/v1/spec_tech.md`\n**Data:** 2026-03-07\n**Branch:** `user-feature`\n\n---\n\n## Visao Geral\n\nEste Task Plan detalha a ordem de implementacao da feature de usuario conforme definido no SPEC_TECH v1. As tasks estao organizadas em ordem de dependencia (bottom-up), seguindo o fluxo: Infraestrutura -> Persistencia -> Repository -> Service -> Handler -> Testes de Integracao/E2E.\n\n---\n\n## Task 1: Pacote Identity (JWT + Contexto)\n\n**Arquivos a CRIAR:**\n- `internal/pkg/identity/identity.go`\n- `internal/pkg/identity/identity_test.go`\n\n**Descricao:**\nCriar o pacote `internal/pkg/identity` com as seguintes funcoes publicas:\n\n1. **Tipo `Claims`** -- struct com `UserID`, `Email` e `jwt.RegisteredClaims`\n2. **`GenerateToken(userID, email, secret string, duration time.Duration) (string, time.Time, error)`** -- gera JWT assinado com HS256\n3. **`ParseToken(tokenString, secret string) (*Claims, error)`** -- valida e extrai claims; rejeita algoritmos que nao sejam HS256\n4. **`WithUserID(ctx context.Context, userID string) context.Context`** -- injeta user_id no contexto via chave unexportada\n5. **`UserIDFromContext(ctx context.Context) (string, bool)`** -- recupera user_id do contexto\n\n**Testes unitarios (`identity_test.go`):**\n- `TestGenerateToken_Success` -- token nao vazio, expiresAt no futuro\n- `TestGenerateToken_TokenIsParseable` -- roundtrip generate -> parse\n- `TestGenerateToken_ClaimsContainCorrectData` -- userID e email corretos\n- `TestParseToken_WrongSecret` -- erro retornado\n- `TestParseToken_ExpiredToken` -- erro retornado\n- `TestParseToken_MalformedToken` -- erro retornado\n- `TestContextRoundtrip_UserID` -- WithUserID + UserIDFromContext retorna valor correto\n- `TestUserIDFromContext_Missing` -- retorna \"\", false\n\n**Criterio de conclusao:** Todos os testes passando com `CGO_ENABLED=1 go test ./internal/pkg/identity/... -v`\n\n**Dependencias:** Nenhuma (pacote isolado).\n\n---\n\n## Task 2: Configuracao -- Adicionar JWTExpirationHours\n\n**Arquivos a MODIFICAR:**\n- `internal/infra/config/config.go`\n- `configs/config.yaml`\n\n**Descricao:**\n\n1. Em `config.go`: adicionar campo `JWTExpirationHours int` na struct `Config` e ler via `v.GetInt(\"jwt.expiration_hours\")`\n2. Em `config.yaml`: adicionar `expiration_hours: 24` dentro da secao `jwt`\n\n**Criterio de conclusao:** `make build` compila sem erros; config carrega o novo campo corretamente.\n\n**Dependencias:** Nenhuma.\n\n---\n\n## Task 3: Migracao -- Adicionar coluna `endereco`\n\n**Arquivos a CRIAR:**\n- `internal/db/migrations/002_add_endereco_usuarios.sql`\n\n**Descricao:**\n\nCriar migracao com:\n```sql\nALTER TABLE usuarios ADD COLUMN endereco TEXT NOT NULL DEFAULT '';\n```\n\nSeguir a convencao do projeto (arquivo unico `.sql`, sem `.up`/`.down`).\n\n**Criterio de conclusao:** Aplicacao inicia sem erros; `PRAGMA table_info(usuarios)` lista coluna `endereco`.\n\n**Dependencias:** Nenhuma.\n\n---\n\n## Task 4: Queries SQLC -- Atualizar queries de usuario\n\n**Arquivos a MODIFICAR:**\n- `internal/db/queries/user.sql` (substituicao total do conteudo)\n\n**Descricao:**\n\nSubstituir o conteudo do arquivo de queries por:\n\n1. **`CreateUser :one`** -- INSERT com campos `id, nome, email, senha_hash, endereco, data_criacao, data_atualizacao` + RETURNING *\n2. **`GetUserByID :one`** -- SELECT * WHERE id = ? LIMIT 1\n3. **`GetUserByEmail :one`** -- SELECT * WHERE email = ? LIMIT 1\n4. **`UpdateUser :one`** -- UPDATE SET nome, email, senha_hash, endereco, data_atualizacao WHERE id = ? RETURNING *\n\n**Pos-execucao:** Executar `make generate` para regenerar `internal/db/sqlc/`.\n\n**Criterio de conclusao:** `make generate` executa sem erros; codigo gerado em `internal/db/sqlc/` contem as novas queries.\n\n**Dependencias:** Task 3 (migracao deve existir para o schema estar correto).\n\n---\n\n## Task 5: Protobuf -- Atualizar contratos gRPC\n\n**Arquivos a MODIFICAR:**\n- `api/proto/v1/user.proto` (substituicao total do conteudo)\n\n**Descricao:**\n\nSubstituir o conteudo do proto conforme SPEC_TECH secao 8.1:\n\n1. Mensagem `User` com campos: id, name, email, address, created_at, updated_at\n2. `CreateUserRequest` com name, email, password, address\n3. `CreateUserResponse` com id, name, email, created_at\n4. `LoginRequest` com email, password\n5. `LoginResponse` com token, expires_at, id, name, email\n6. `GetUserLoggedRequest` (vazio)\n7. `GetUserLoggedResponse` com User\n8. `UpdateUserRequest` com optional string para name, email, address, new_password, current_password\n9. `UpdateUserResponse` com User\n10. Servico `UserService` com 4 RPCs: CreateUser, Login, GetUserLogged, UpdateUser\n\n**Pos-execucao:** Executar `make generate` para regenerar `gen/proto/v1/`.\n\n**Criterio de conclusao:** `make generate` e `make build` executam sem erros.\n\n**Dependencias:** Nenhuma (pode ser feita em paralelo com Tasks 1-4).\n\n---\n\n## Task 6: Repository -- Estender UserRepository\n\n**Arquivos a MODIFICAR:**\n- `internal/repository/user_repository.go`\n\n**Descricao:**\n\n1. Adicionar campo `Address string` ao modelo `User`\n2. Adicionar metodos a interface `UserRepository`:\n - `GetByEmail(ctx context.Context, email string) (*User, error)`\n - `Update(ctx context.Context, user *User) (*User, error)`\n3. Implementar os novos metodos na struct `userRepository`\n4. Atualizar mapeamento SQLC->dominio no `Create` e `GetByID` para incluir `Address` (endereco -> Address)\n5. Implementar mapeamento no `GetByEmail` e `Update`\n\n**Criterio de conclusao:** `make build` compila sem erros.\n\n**Dependencias:** Task 4 (queries SQLC regeneradas).\n\n---\n\n## Task 7: Service -- Estender UserService\n\n**Arquivos a MODIFICAR:**\n- `internal/service/user_service.go`\n\n**Descricao:**\n\n1. **Adicionar dependencias ao construtor:**\n - `*config.Config` e `*zap.Logger` como parametros de `NewUserService`\n - FX resolve automaticamente (sem alterar `fx.go`)\n\n2. **Adicionar variaveis de erro exportadas:**\n - `ErrInvalidName`, `ErrInvalidEmail`, `ErrEmailAlreadyExists`, `ErrPasswordTooShort`, `ErrAddressRequired`\n - `ErrInvalidCredentials`, `ErrUserNotFound`, `ErrNoFieldsToUpdate`\n - `ErrCurrentPasswordRequired`, `ErrCurrentPasswordWrong`\n\n3. **Adicionar tipos:**\n - `CreateUserInput` -- atualizar com campo `Address`\n - `LoginResult` -- novo (Token, ExpiresAt, User)\n - `UpdateUserInput` -- novo (ponteiros *string para campos opcionais)\n\n4. **Adicionar metodos a interface `UserService`:**\n - `Login(ctx context.Context, email, password string) (*LoginResult, error)`\n - `GetUserLogged(ctx context.Context, userID string) (*repository.User, error)`\n - `UpdateUser(ctx context.Context, userID string, input UpdateUserInput) (*repository.User, error)`\n\n5. **Implementar `CreateUser` (atualizar):**\n - Adicionar validacoes: name vazio, email (net/mail), senha >= 8 chars, address vazio\n - Verificar email unico via `repo.GetByEmail`\n - Hash com bcrypt, UUID, timestamps\n - Persistir via `repo.Create`\n - Logging: Info \"usuario criado com sucesso\"\n\n6. **Implementar `Login`:**\n - `repo.GetByEmail` -- se nao existe: `ErrInvalidCredentials`\n - `bcrypt.CompareHashAndPassword` -- se falha: `ErrInvalidCredentials`\n - `identity.GenerateToken(userID, email, secret, duration)`\n - Limpar PasswordHash antes de retornar\n - Logging: Info \"login bem-sucedido\" / Warn \"tentativa de login falha\"\n\n7. **Implementar `GetUserLogged`:**\n - `repo.GetByID(userID)` -- se nao encontra: `ErrUserNotFound`\n - Limpar PasswordHash\n - Retornar User\n\n8. **Implementar `UpdateUser`:**\n - Validar ao menos 1 campo nao-nil\n - Buscar user atual via `repo.GetByID`\n - Se email: validar formato + unicidade\n - Se new_password: exigir current_password, validar tamanho, verificar hash, gerar novo hash\n - Aplicar campos nao-nil sobre user atual\n - Persistir via `repo.Update`\n - Limpar PasswordHash antes de retornar\n - Logging: Info \"dados atualizados\" / Info \"senha alterada\"\n\n**Criterio de conclusao:** `make build` compila sem erros.\n\n**Dependencias:** Task 1 (identity), Task 2 (config), Task 6 (repository).\n\n---\n\n## Task 8: Interceptor Auth -- Integrar com identity\n\n**Arquivos a MODIFICAR:**\n- `internal/infra/grpc/interceptors/auth.go`\n\n**Descricao:**\n\n1. Adicionar `/proto.v1.UserService/Login` em `skipMethods`\n2. Substituir parse manual do token por `identity.ParseToken(tokenString, cfg.JWTSecret)`\n3. Apos validacao bem-sucedida: `ctx = identity.WithUserID(ctx, claims.UserID)`\n4. Passar ctx atualizado ao handler\n\n**Criterio de conclusao:** `make build` compila sem erros.\n\n**Dependencias:** Task 1 (identity).\n\n---\n\n## Task 9: Handler gRPC -- Estender UserHandler\n\n**Arquivos a MODIFICAR:**\n- `internal/handler/grpc/user_handler.go`\n\n**Descricao:**\n\n1. **Atualizar `CreateUser`:**\n - Extrair `address` do request\n - Mapear response com `CreateUserResponse{id, name, email, created_at}`\n\n2. **Implementar `Login`:**\n - Extrair email e password do request\n - Chamar `service.Login`\n - Retornar `LoginResponse{token, expires_at, id, name, email}`\n\n3. **Implementar `GetUserLogged`:**\n - `identity.UserIDFromContext(ctx)` -- se ausente: `codes.Unauthenticated`\n - Chamar `service.GetUserLogged(userID)`\n - Retornar `GetUserLoggedResponse{User{...}}`\n\n4. **Implementar `UpdateUser`:**\n - `identity.UserIDFromContext(ctx)` -- se ausente: `codes.Unauthenticated`\n - Construir `UpdateUserInput` com ponteiros (nil = campo ausente)\n - Chamar `service.UpdateUser(userID, input)`\n - Retornar `UpdateUserResponse{User{...}}`\n\n5. **Remover `GetUser`** (substituido por `GetUserLogged`)\n\n6. **Atualizar `mapServiceError`:**\n - Adicionar mapeamento para todos os novos erros conforme SPEC_TECH secao 11.2\n\n7. **Criar funcao auxiliar `domainUserToProto`:**\n - Converte `*repository.User` para `*pb.User`\n\n**Criterio de conclusao:** `make build` compila sem erros.\n\n**Dependencias:** Task 5 (proto gerado), Task 7 (service).\n\n---\n\n## Task 10: Configuracao de Rotas Publicas\n\n**Arquivos a MODIFICAR:**\n- `configs/config.yaml`\n\n**Descricao:**\n\nGarantir que `skip_methods` no config inclua:\n- `/proto.v1.UserService/CreateUser`\n- `/proto.v1.UserService/Login`\n\n**Criterio de conclusao:** Servidor inicia sem erros; rotas publicas acessiveis sem token.\n\n**Dependencias:** Task 8 (interceptor), Task 9 (handler).\n\n---\n\n## Task 11: Testes Unitarios -- Service\n\n**Arquivos a MODIFICAR:**\n- `internal/service/user_service_test.go`\n\n**Descricao:**\n\nAtualizar mock `MockUserRepository` com metodos `GetByEmail` e `Update`.\n\n**CreateUser (atualizar existentes + novos):**\n- `TestCreateUser_Success` -- incluir Address\n- `TestCreateUser_PasswordIsHashed` -- bcrypt roundtrip\n- `TestCreateUser_GeneratesValidUUID`\n- `TestCreateUser_ErrorEmptyName`\n- `TestCreateUser_ErrorInvalidEmailFormat` (novo)\n- `TestCreateUser_ErrorPasswordTooShort_7Chars` (novo)\n- `TestCreateUser_PasswordExactly8Chars` (novo)\n- `TestCreateUser_ErrorEmptyAddress` (novo)\n- `TestCreateUser_ErrorRepositoryFailure`\n\n**Login (novos):**\n- `TestLogin_Success`\n- `TestLogin_ResultDoesNotExposePasswordHash`\n- `TestLogin_TokenExpiresAtRespectsConfig`\n- `TestLogin_EmailNotFound_GenericError`\n- `TestLogin_WrongPassword_GenericError`\n- `TestLogin_ErrorRepositoryFailure`\n\n**GetUserLogged (novos):**\n- `TestGetUserLogged_Success`\n- `TestGetUserLogged_UserNotFound`\n- `TestGetUserLogged_RepositoryError`\n\n**UpdateUser (novos):**\n- `TestUpdateUser_NameOnly`\n- `TestUpdateUser_EmailOnly`\n- `TestUpdateUser_AddressOnly`\n- `TestUpdateUser_PasswordChange_Success`\n- `TestUpdateUser_MultipleFields`\n- `TestUpdateUser_NoFieldsToUpdate`\n- `TestUpdateUser_InvalidEmailFormat`\n- `TestUpdateUser_EmailAlreadyExists`\n- `TestUpdateUser_CurrentPasswordMissing`\n- `TestUpdateUser_WrongCurrentPassword`\n- `TestUpdateUser_NewPasswordTooShort`\n- `TestUpdateUser_UserNotFound`\n- `TestUpdateUser_RepositoryFailure`\n\n**Criterio de conclusao:** `CGO_ENABLED=1 go test ./internal/service/... -v` -- todos passando.\n\n**Dependencias:** Task 7 (service implementado).\n\n---\n\n## Task 12: Testes Unitarios -- Handler\n\n**Arquivos a MODIFICAR:**\n- `internal/handler/grpc/user_handler_test.go`\n\n**Descricao:**\n\nAtualizar mock `MockUserService` com metodos `Login`, `GetUserLogged`, `UpdateUser`. Remover testes de `GetUser`.\n\n**CreateUser:**\n- `TestUserHandler_CreateUser_Success`\n- `TestUserHandler_CreateUser_ResponseDoesNotExposePasswordHash`\n- `TestUserHandler_CreateUser_EmptyName`\n- `TestUserHandler_CreateUser_EmptyEmail`\n- `TestUserHandler_CreateUser_ShortPassword`\n- `TestUserHandler_CreateUser_EmptyAddress`\n- `TestUserHandler_CreateUser_DuplicateEmail`\n- `TestUserHandler_CreateUser_InternalError`\n\n**Login:**\n- `TestUserHandler_Login_Success`\n- `TestUserHandler_Login_InvalidCredentials`\n- `TestUserHandler_Login_InternalError`\n\n**GetUserLogged:**\n- `TestUserHandler_GetUserLogged_Success`\n- `TestUserHandler_GetUserLogged_MissingUserID`\n- `TestUserHandler_GetUserLogged_NotFound`\n\n**UpdateUser:**\n- `TestUserHandler_UpdateUser_NameOnly`\n- `TestUserHandler_UpdateUser_EmailOnly`\n- `TestUserHandler_UpdateUser_PasswordChange`\n- `TestUserHandler_UpdateUser_MultipleFields`\n- `TestUserHandler_UpdateUser_NoFields`\n- `TestUserHandler_UpdateUser_EmailAlreadyExists`\n- `TestUserHandler_UpdateUser_CurrentPasswordMissing`\n- `TestUserHandler_UpdateUser_WrongCurrentPassword`\n- `TestUserHandler_UpdateUser_NewPasswordTooShort`\n- `TestUserHandler_UpdateUser_UserNotFound`\n\n**Criterio de conclusao:** `CGO_ENABLED=1 go test ./internal/handler/grpc/... -v` -- todos passando.\n\n**Dependencias:** Task 9 (handler implementado).\n\n---\n\n## Task 13: Testes Unitarios -- Interceptor Auth\n\n**Arquivos a MODIFICAR:**\n- `internal/infra/grpc/interceptors/auth_test.go`\n\n**Descricao:**\n\nAdicionar testes:\n- `TestAuthInterceptor_ValidToken_InjectsUserID` -- ctx contem user_id correto\n- `TestAuthInterceptor_SkipsLogin` -- table-driven com CreateUser e Login\n- `TestAuthInterceptor_ProtectedMethodsRequireAuth` -- table-driven com GetUserLogged e UpdateUser\n\n**Criterio de conclusao:** `CGO_ENABLED=1 go test ./internal/infra/grpc/interceptors/... -v` -- todos passando.\n\n**Dependencias:** Task 8 (interceptor atualizado).\n\n---\n\n## Task 14: Testes de Integracao -- Repository\n\n**Arquivos a CRIAR:**\n- `internal/repository/user_repository_integration_test.go`\n\n**Descricao:**\n\nSetup com SQLite em `t.TempDir()` + migracoes aplicadas via `database.NewDatabase`.\n\nTestes:\n- `TestUserRepository_Create_Success` -- todos os campos persistidos\n- `TestUserRepository_CreateAndGetByID` -- roundtrip create + get\n- `TestUserRepository_GetByEmail` -- retorna user correto com Address\n- `TestUserRepository_Create_DuplicateEmail` -- erro UNIQUE constraint\n- `TestUserRepository_GetByID_NotFound` -- erro\n- `TestUserRepository_GetByEmail_NotFound` -- erro\n- `TestUserRepository_Update_NameOnly`\n- `TestUserRepository_Update_Email`\n- `TestUserRepository_Update_PasswordHash`\n- `TestUserRepository_Update_Address`\n- `TestUserRepository_Update_UpdatedAtChanges`\n- `TestUserRepository_Create_GeneratesValidUUID`\n\n**Criterio de conclusao:** `CGO_ENABLED=1 go test ./internal/repository/... -v -tags=integration` -- todos passando.\n\n**Dependencias:** Task 6 (repository implementado), Task 3 (migracao).\n\n---\n\n## Task 15: Testes de Integracao -- Migracao\n\n**Arquivos a MODIFICAR:**\n- `internal/infra/database/sqlite_test.go`\n\n**Descricao:**\n\nAdicionar testes:\n- `TestRunMigrations_TableSchema_IncludesEndereco` -- PRAGMA table_info confirma coluna\n- `TestRunMigrations_EnderecocolumnDefaultEmpty` -- INSERT sem endereco retorna \"\"\n- `TestRunMigrations_IdempotentWithEndereco` -- segunda chamada NewDatabase sem erro\n\n**Criterio de conclusao:** `CGO_ENABLED=1 go test ./internal/infra/database/... -v` -- todos passando.\n\n**Dependencias:** Task 3 (migracao criada).\n\n---\n\n## Task 16: Testes E2E\n\n**Arquivos a CRIAR:**\n- `internal/e2e/user_e2e_test.go`\n\n**Descricao:**\n\nSetup: servidor gRPC real em porta efemera + cliente gRPC + SQLite em `t.TempDir()`.\n\nFluxos:\n- `TestE2E_CreateUser_FullFlow` -- cadastro completo, verifica response sem token, UUID valido\n- `TestE2E_LoginAndGetUserLogged_FullFlow` -- login + consulta dados com Bearer token\n- `TestE2E_UpdateUser_PartialUpdate` -- login + update nome + get confirma persistencia\n- `TestE2E_ProtectedRoutes_RequireAuth` -- GetUserLogged e UpdateUser sem token retornam Unauthenticated\n- `TestE2E_UpdateUser_PasswordChangeAndReauth` -- troca senha + login nova senha ok + login senha antiga falha\n\n**Criterio de conclusao:** `CGO_ENABLED=1 go test ./internal/e2e/... -v` -- todos passando.\n\n**Dependencias:** Todas as tasks anteriores (1-15).\n\n---\n\n## Task 17: Validacao Final\n\n**Descricao:**\n\n1. Executar `make build` -- compilacao sem erros\n2. Executar `make test` -- todos os testes passando\n3. Executar `make lint` -- sem problemas de lint nos protos\n4. Verificar que `PasswordHash` e tokens JWT nao aparecem em nenhum log ou response\n5. Verificar que rotas publicas (`CreateUser`, `Login`) funcionam sem token\n6. Verificar que rotas protegidas (`GetUserLogged`, `UpdateUser`) exigem token valido\n\n**Criterio de conclusao:** Todos os checks passam. Feature pronta para code review.\n\n**Dependencias:** Task 16.\n\n---\n\n## Diagrama de Dependencias\n\n```\nTask 1 (identity) \u00e2\u201d\u20ac\u00e2\u201d\u20ac\u00e2\u201d\u20ac\u00e2\u201d\u20ac\u00e2\u201d\u20ac\u00e2\u201d\u20ac\u00e2\u201d\u20ac\u00e2\u201d\u20ac\u00e2\u201d\u20ac\u00e2\u201d\u20ac\u00e2\u201d\u20ac\u00e2\u201d\u20ac\u00e2\u201d\u20ac\u00e2\u201d\u00ac\u00e2\u201d\u20ac\u00e2\u201d\u20ac\u00e2\u201d\u20ac\u00e2\u201d\u20ac\u00e2\u201d\u20ac\u00e2\u201d\u20ac> Task 7 (service) \u00e2\u201d\u20ac\u00e2\u201d\u20ac\u00e2\u201d\u20ac\u00e2\u201d\u20ac\u00e2\u201d\u20ac\u00e2\u201d\u20ac> Task 9 (handler) \u00e2\u201d\u20ac\u00e2\u201d\u20ac> Task 12 (test handler)\n \u00e2\u201d\u201a \u00e2\u201d\u201a \u00e2\u201d\u201a\nTask 2 (config) \u00e2\u201d\u20ac\u00e2\u201d\u20ac\u00e2\u201d\u20ac\u00e2\u201d\u20ac\u00e2\u201d\u20ac\u00e2\u201d\u20ac\u00e2\u201d\u20ac\u00e2\u201d\u20ac\u00e2\u201d\u20ac\u00e2\u201d\u20ac\u00e2\u201d\u20ac\u00e2\u201d\u20ac\u00e2\u201d\u20ac\u00e2\u201d\u20ac\u00e2\u201d\u20ac\u00e2\u201d\u20ac\u00e2\u201d\u02dc \u00e2\u201d\u201a \u00e2\u201d\u201a\n \u00e2\u2013\u00bc \u00e2\u2013\u00bc\nTask 3 (migracao) \u00e2\u201d\u20ac\u00e2\u201d\u20ac> Task 4 (sqlc) \u00e2\u201d\u20ac\u00e2\u201d\u20ac> Task 6 (repo) \u00e2\u201d\u20ac\u00e2\u201d\u20ac> Task 11 (test service) Task 10 (rotas publicas)\n \u00e2\u201d\u201a \u00e2\u201d\u201a\n \u00e2\u2013\u00bc \u00e2\u2013\u00bc\nTask 15 (test migracao) Task 14 (test integracao repo)\n\nTask 1 \u00e2\u201d\u20ac\u00e2\u201d\u20ac> Task 8 (interceptor) \u00e2\u201d\u20ac\u00e2\u201d\u20ac> Task 13 (test interceptor)\n\nTodas as tasks \u00e2\u201d\u20ac\u00e2\u201d\u20ac> Task 16 (E2E) \u00e2\u201d\u20ac\u00e2\u201d\u20ac> Task 17 (validacao final)\n```\n\n## Resumo de Execucao\n\n| Fase | Tasks | Descricao |\n|------|-------|-----------|\n| **Fase 1 -- Infraestrutura** | Tasks 1, 2, 3 | Identity, Config, Migracao (paralelas) |\n| **Fase 2 -- Persistencia** | Tasks 4, 5 | Queries SQLC, Protobuf (paralelas) |\n| **Fase 3 -- Camadas de Negocio** | Tasks 6, 7, 8 | Repository, Service, Interceptor |\n| **Fase 4 -- Exposicao** | Tasks 9, 10 | Handler gRPC, Rotas publicas |\n| **Fase 5 -- Testes Unitarios** | Tasks 11, 12, 13 | Testes service, handler, interceptor (paralelas) |\n| **Fase 6 -- Testes Integracao** | Tasks 14, 15 | Repository + Migracao (paralelas) |\n| **Fase 7 -- Testes E2E + Validacao** | Tasks 16, 17 | Fluxos completos + validacao final |\n\n**Total: 17 tasks organizadas em 7 fases.**\n"}], "grading": {"eval_id": 1, "eval_name": "happy-path-spec-tech-usuario", "configuration": "without_skill", "expectations": [{"text": "Extrai o nome da feature do SPEC_TECH e confirma com o usuario antes de prosseguir", "passed": false, "evidence": "Nao extraiu nem confirmou o nome da feature com o usuario. Pulou direto para gerar o plano completo sem interacao."}, {"text": "Faz apenas UMA pergunta por vez (nao bombardeia com multiplas perguntas no mesmo turno)", "passed": false, "evidence": "Nao fez nenhuma pergunta. Gerou o plano inteiro (17 tasks, 7 fases) em um unico turno sem interacao."}, {"text": "Propoe macro-fases de alto nivel e aguarda validacao antes de criar tasks", "passed": false, "evidence": "Gerou fases E tasks detalhadas simultaneamente. Nao aguardou validacao das fases antes de criar as tasks."}, {"text": "Le as rules do projeto (.claude/rules/ e CLAUDE.md) ANTES de gerar tasks", "passed": false, "evidence": "Transcript mostra apenas leitura do SPEC_TECH e um glob de arquivos Go. Nao leu .claude/rules/ nem CLAUDE.md."}, {"text": "NAO inicia execucao automatica de tasks \u00e2\u20ac\u201d apenas apresenta o plano e aguarda", "passed": true, "evidence": "Apresentou o plano completo sem iniciar execucao automatica."}]}}, {"id": "eval-2-spec-simples-with_skill", "prompt": "Preciso de um TASK PLAN para esta feature simples. O SPEC_TECH aprovado esta abaixo:\n\n---\n# SPEC_TECH \u00e2\u20ac\u201d Adicionar Telefone ao Usuario v1\n\n## 1. Identificacao\n| Campo | Valor |\n|-------|-------|\n| **Feature/Projeto** | Adicionar Telefone ao Modulo de Usuario |\n| **Versao** | v1 |\n| **Data** | 2026-03-07 |\n| **PRD** | docs/feature-telefone-usuario/v1/prd.md |\n\n## 2. Resumo Tecnico\nAdicionar campo `telefone` (opcional) a tabela `usuarios` e expor via gRPC nos endpoints CreateUser e UpdateUser.\n\n## 3. Definicoes Tecnicas\n\n### 3.1 Banco de Dados\n- Nova migracao: `ALTER TABLE usuarios ADD COLUMN telefone TEXT;`\n\n### 3.2 SQLC\n- Atualizar queries CreateUser e UpdateUser para incluir coluna `telefone`\n\n### 3.3 Repository\n- Adicionar campo `Phone` ao modelo User\n- Mapear `telefone` (banco) para `Phone` (Go)\n\n### 3.4 Service\n- Aceitar campo opcional phone em CreateUser e UpdateUser\n- Validacao: se informado, deve ter entre 10 e 15 caracteres\n\n### 3.5 Handler gRPC\n- Adicionar campo `phone` nas mensagens proto CreateUserRequest, UpdateUserRequest e User\n\n### 3.6 Proto\n- Atualizar user.proto com campo `string phone = X;`\n\n## 4. User Stories Relacionadas\n- US-10: Como usuario, quero informar meu telefone para receber notificacoes por SMS\n---", "eval_id": 2, "outputs": [{"name": "response.md", "type": "text", "content": "# Resposta Completa \u00e2\u20ac\u201d TASK PLAN: Adicionar Telefone ao Usuario\n\n## Simulacao do Fluxo Interativo\n\n---\n\n### Passo 1: Receber e ler o SPEC_TECH\n\nSPEC_TECH recebido e lido com sucesso.\n\n### Passo 2: Extrair nome da feature\n\nNome extraido da secao \"1. Identificacao\", campo \"Feature/Projeto\": **Adicionar Telefone ao Modulo de Usuario**\n\nNome em kebab-case: `telefone-usuario`\nDiretorio: `docs/telefone-usuario/v1/`\n\n### Passo 3: Confirmar nome\n\n> **Pergunta que seria feita ao usuario:**\n> \"Obrigado! Vamos iniciar o TASK PLAN para **Adicionar Telefone ao Modulo de Usuario**. Podemos iniciar a definicao macro das fases?\"\n\n> **Resposta simulada do usuario:** \"Sim, pode iniciar.\"\n\n### Passo 4: Analise obrigatoria do projeto\n\n**Analise realizada do codebase:**\n\n1. **Migracao existente:** `internal/db/migrations/001_create_usuarios.sql` \u00e2\u20ac\u201d tabela `usuarios` com colunas `id`, `nome`, `email`, `senha_hash`, `data_criacao`, `data_atualizacao`. NAO possui coluna `telefone`.\n2. **Queries SQLC:** `internal/db/sqlc/queries/user.sql` \u00e2\u20ac\u201d queries `CreateUser` e `GetUserByID`. NAO inclui `telefone`.\n3. **Repository:** `internal/repository/user_repository.go` \u00e2\u20ac\u201d struct `User` com campos `ID`, `Name`, `Email`, `PasswordHash`, `CreatedAt`, `UpdatedAt`. NAO possui `Phone`. Interface `UserRepository` com metodos `Create` e `GetByID`.\n4. **Service:** `internal/service/user_service.go` \u00e2\u20ac\u201d struct `CreateUserInput` com `Name`, `Email`, `Password`. NAO possui `Phone`. Interface `UserService` com `CreateUser` e `GetUser`. NAO possui `UpdateUser`.\n5. **Handler:** `internal/handler/grpc/user_handler.go` \u00e2\u20ac\u201d handler com `CreateUser` e `GetUser`. NAO possui `UpdateUser`.\n6. **Proto:** `api/proto/v1/user.proto` \u00e2\u20ac\u201d messages `CreateUserRequest`, `CreateUserResponse`, `GetUserRequest`, `GetUserResponse`. NAO possui `UpdateUser` nem campo `phone`.\n7. **Testes existentes:** `user_service_test.go` (table-driven, testify/mock) e `user_handler_test.go` (testify/mock, gRPC status codes).\n8. **Padrao de DI:** Uber FX modules em cada pacote, composicao em `internal/infra/di/fx.go`.\n9. **Servidor gRPC:** `internal/infra/grpc/server.go` \u00e2\u20ac\u201d registra `UserServiceServer`.\n\n**Observacao critica:** O SPEC_TECH menciona \"UpdateUser\" mas o endpoint `UpdateUser` NAO existe atualmente no projeto. Isso significa que a feature precisa CRIAR o endpoint UpdateUser completo (proto, handler, service, repository, queries SQLC, migracao).\n\n### Passo 5: Propor macro-fases\n\n> **Pergunta que seria feita ao usuario:**\n> \"Analisei o codebase e identifiquei que o endpoint UpdateUser NAO existe atualmente. O SPEC_TECH menciona atualizar CreateUser e UpdateUser para incluir telefone. Proponho as seguintes macro-fases:\n>\n> - **Fase 1 -- Preparacao / Banco de Dados**: Migracao para adicionar coluna `telefone` e atualizacao das queries SQLC\n> - **Fase 2 -- Implementacao Backend**: Atualizar repository, service e criar UpdateUser completo (repository + service) com campo phone\n> - **Fase 3 -- Interface gRPC**: Atualizar proto, handler CreateUser e criar handler UpdateUser com campo phone\n> - **Fase 4 -- Testes e Validacao**: Atualizar testes existentes e criar novos testes para o campo phone e endpoint UpdateUser\n>\n> Podemos seguir com estas fases?\"\n\n> **Resposta simulada do usuario:** \"Sim, pode seguir. O UpdateUser faz parte do escopo, pode criar do zero.\"\n\n### Passo 6: Destrinchar tasks\n\n> **Pergunta que seria feita ao usuario:**\n> \"Podemos destrinchar as tasks da Fase 1?\"\n\n> **Resposta simulada do usuario:** \"Sim.\"\n\nApos analise detalhada do codebase e do SPEC_TECH, as tasks foram definidas conforme abaixo.\n\n---\n\n## TASK PLAN (documento de referencia)\n\n---\n\n# TASK PLAN -- Plano de Execucao das Tasks\n\n## 1. Identificacao\n- **Feature/Projeto**: Adicionar Telefone ao Modulo de Usuario\n- **Responsavel (Tech Lead)**: N/A (definido pelo time)\n- **Data**: 2026-03-07\n- **Status**: Rascunho\n- **SPEC Relacionado**: docs/feature-telefone-usuario/v1/spec_tech.md (inline no prompt)\n- **PRD Relacionado**: docs/feature-telefone-usuario/v1/prd.md\n\n---\n\n## 2. Objetivo do Task Plan\nAdicionar o campo opcional `telefone` a tabela `usuarios` no banco de dados SQLite, expor esse campo nos endpoints gRPC `CreateUser` e `GetUser`, e criar o endpoint completo `UpdateUser` com suporte ao campo `phone`. Ao final, o usuario podera informar, visualizar e atualizar seu telefone via API gRPC.\n\n---\n\n## 3. Macro-Fases (alto nivel)\n- **Fase 1 -- Preparacao / Banco de Dados**\n - Objetivo: Adicionar coluna `telefone` ao schema e atualizar queries SQLC\n - Tasks: T1, T2\n- **Fase 2 -- Implementacao Backend**\n - Objetivo: Atualizar repository e service para suportar campo phone e criar UpdateUser\n - Tasks: T3, T4\n- **Fase 3 -- Interface gRPC**\n - Objetivo: Atualizar proto e handlers para expor campo phone e endpoint UpdateUser\n - Tasks: T5, T6\n- **Fase 4 -- Testes e Validacao**\n - Objetivo: Atualizar testes existentes e criar novos testes\n - Tasks: (testes incluidos em cada task individual)\n\n---\n\n## 4. Lista de Tasks (visao macro)\n| ID | Nome da Task | Arquivo | Fase | Dependencias | Pode Rodar em Paralelo? | Status |\n| --- | ----------------------------------------------- | ----------------- | ---- | ------------ | ----------------------- | ------- |\n| T1 | Criar migracao para adicionar coluna telefone | [T1](tasks/T1.md) | 1 | Nenhuma | Sim (com T5) | A Fazer |\n| T2 | Atualizar queries SQLC para incluir telefone | [T2](tasks/T2.md) | 1 | T1 | Nao | A Fazer |\n| T3 | Atualizar repository com campo Phone | [T3](tasks/T3.md) | 2 | T2 | Nao | A Fazer |\n| T4 | Atualizar service com campo Phone e UpdateUser | [T4](tasks/T4.md) | 2 | T3 | Nao | A Fazer |\n| T5 | Atualizar proto com campo phone e UpdateUser | [T5](tasks/T5.md) | 3 | Nenhuma | Sim (com T1) | A Fazer |\n| T6 | Atualizar handler com campo phone e UpdateUser | [T6](tasks/T6.md) | 3 | T4, T5 | Nao | A Fazer |\n\n---\n\n## 5. Rastreabilidade: User Stories -> Tasks\n\n| User Story (PRD) | Definicao Tecnica (SPEC) | Tasks Relacionadas | Status |\n| ---------------- | --------------------------------------------------------------------- | ------------------ | ------- |\n| US-10 | 3.1 Banco de Dados: ALTER TABLE adicionar coluna telefone | T1 | A Fazer |\n| US-10 | 3.2 SQLC: Atualizar queries CreateUser e UpdateUser | T2 | A Fazer |\n| US-10 | 3.3 Repository: Adicionar campo Phone ao modelo User | T3 | A Fazer |\n| US-10 | 3.4 Service: Aceitar campo phone, validacao 10-15 caracteres | T4 | A Fazer |\n| US-10 | 3.5/3.6 Handler/Proto: Adicionar campo phone nas mensagens e UpdateUser | T5, T6 | A Fazer |\n\n> Esta tabela garante que TODAS as user stories do PRD tem tasks correspondentes para implementacao.\n\n---\n\n## 6. Dependencias Gerais\n\n- T1 (migracao) e T5 (proto) podem rodar em paralelo pois sao independentes\n- T2 depende de T1 (queries precisam da coluna no schema)\n- T3 depende de T2 (repository usa codigo gerado pelo SQLC)\n- T4 depende de T3 (service depende do repository)\n- T6 depende de T4 e T5 (handler depende do service e do proto gerado)\n- Apos T2 e T5, e necessario executar `make generate` (buf + sqlc) para regenerar codigo\n\n**Grafo de dependencias:**\n```\nT1 --> T2 --> T3 --> T4 --\\\n --> T6\nT5 ----------------------------/\n```\n\n---\n\n## 7. Criterios de Conclusao da Feature\nA feature sera considerada concluida quando:\n- [x] Coluna `telefone` existe na tabela `usuarios`\n- [ ] Queries SQLC incluem `telefone` em CreateUser, UpdateUser e GetUserByID\n- [ ] Modelo `User` no repository possui campo `Phone`\n- [ ] Service aceita e valida campo `phone` (10-15 caracteres quando informado)\n- [ ] Endpoint CreateUser aceita campo `phone` opcional\n- [ ] Endpoint UpdateUser criado e funcional com campo `phone`\n- [ ] Campo `phone` retornado no GetUser\n- [ ] Todos os testes existentes atualizados e passando\n- [ ] Novos testes para validacao de telefone criados e passando\n- [ ] Nenhum comportamento divergente do PRD\n- [ ] Todas as User Stories cobertas (tabela secao 5)\n\n---\n\n## 8. Riscos & Mitigacoes\n- **Risco 1**: Migracao ALTER TABLE em SQLite pode ter limitacoes \u00e2\u2020\u2019 Mitigacao: `ALTER TABLE ADD COLUMN` e suportado normalmente no SQLite para adicionar colunas\n- **Risco 2**: Queries SQLC existentes podem quebrar apos adicionar coluna \u00e2\u2020\u2019 Mitigacao: Usar `SELECT *` ja existente nas queries garante que a nova coluna sera retornada automaticamente\n- **Risco 3**: Testes existentes podem falhar se mock nao incluir campo Phone \u00e2\u2020\u2019 Mitigacao: Atualizar mocks e assertions em cada task que modifica interfaces\n\n---\n\n## 9. Checklist Final\n- [x] Task Plan completo\n- [x] Tasks mapeadas\n- [x] Dependencias validadas\n- [x] Rastreabilidade User Stories -> Tasks preenchida\n- [x] Pronto para execucao paralela\n\n---\n---\n\n## TASKS INDIVIDUAIS\n\n---\n\n# T1 -- Criar migracao para adicionar coluna telefone\n\n## 1. Identificacao\n- **ID**: T1\n- **Nome da Task**: Criar migracao para adicionar coluna telefone\n- **Responsavel**: N/A\n- **Status**: A Fazer\n- **Fase**: 1 -- Preparacao / Banco de Dados\n- **Dependencias**: Nenhuma\n- **User Stories Relacionadas**: US-10\n\n---\n\n## 2. Objetivo da Task\nCriar arquivo de migracao SQL que adiciona a coluna `telefone` (TEXT, opcional) a tabela `usuarios`.\n\n---\n\n## 3. Descricao Detalhada\nCriar um novo arquivo de migracao seguindo a convencao de nomenclatura do projeto:\n\n1. **Arquivo**: `internal/db/migrations/002_add_telefone_usuarios.sql`\n2. **Conteudo**: Instrucao `ALTER TABLE usuarios ADD COLUMN telefone TEXT;`\n3. A coluna e opcional (sem `NOT NULL`), pois o telefone nao e obrigatorio\n4. NAO editar a migracao existente `001_create_usuarios.sql` (regra de imutabilidade de migracoes)\n5. A migracao sera executada automaticamente no boot da aplicacao via golang-migrate\n\n**Observacao**: O projeto usa apenas migracoes `.sql` (sem separacao up/down em arquivos distintos com base no que existe). Seguir o mesmo padrao da migracao `001_create_usuarios.sql`.\n\n---\n\n## 4. Aceite Tecnico (criterios objetivos)\nA task estara concluida quando:\n- [ ] Arquivo `002_add_telefone_usuarios.sql` criado em `internal/db/migrations/`\n- [ ] SQL contem `ALTER TABLE usuarios ADD COLUMN telefone TEXT;`\n- [ ] Coluna e opcional (sem NOT NULL)\n- [ ] Migracao 001 NAO foi editada\n- [ ] Nenhuma quebra nos fluxos existentes\n\n---\n\n## 5. Arquivos Impactados\n\n### 5.1 Arquivos a Criar\n| Arquivo | Descricao |\n|---------|-----------|\n| `internal/db/migrations/002_add_telefone_usuarios.sql` | Migracao para adicionar coluna telefone |\n\n### 5.2 Arquivos a Modificar\n| Arquivo | Modificacao |\n|---------|------------|\n| Nenhum | N/A |\n\n### 5.3 Arquivos de Referencia\n| Arquivo | Motivo da Consulta |\n|---------|-------------------|\n| `internal/db/migrations/001_create_usuarios.sql` | Referencia de padrao de migracao existente |\n\n---\n\n## 6. Testes\n\n### 6.1 Testes Unitarios\nN/A -- migracao SQL nao possui testes unitarios isolados.\n\n### 6.2 Testes de Integracao\n- [ ] Teste: Verificar que a migracao aplica corretamente e a coluna `telefone` existe na tabela `usuarios` apos execucao\n- [ ] Teste: Verificar que registros existentes na tabela `usuarios` continuam acessiveis apos a migracao (coluna `telefone` com valor NULL)\n\n### 6.3 Testes E2E\nN/A -- validacao sera feita indiretamente pelos testes das camadas superiores.\n\n### 6.4 Cenarios de Erro\n- [ ] Cenario: Migracao re-executada nao causa erro (idempotencia \u00e2\u20ac\u201d verificar comportamento do golang-migrate)\n\n---\n\n## 7. Notas / Observacoes\n- SQLite suporta `ALTER TABLE ADD COLUMN` normalmente\n- A coluna sera NULL para usuarios existentes, o que e o comportamento desejado (campo opcional)\n- O projeto usa um unico arquivo `.sql` por migracao (nao separa up/down em arquivos distintos), conforme padrao observado em `001_create_usuarios.sql`\n\n---\n\n## 8. Checklist Final\n- [ ] Implementada conforme SPEC\n- [ ] Testes unitarios criados/atualizados\n- [ ] Testes de integracao criados/atualizados\n- [ ] Aceite tecnico atendido\n- [ ] Revisada\n- [ ] Integrada a branch principal\n\n---\n---\n\n# T2 -- Atualizar queries SQLC para incluir telefone\n\n## 1. Identificacao\n- **ID**: T2\n- **Nome da Task**: Atualizar queries SQLC para incluir telefone\n- **Responsavel**: N/A\n- **Status**: A Fazer\n- **Fase**: 1 -- Preparacao / Banco de Dados\n- **Dependencias**: T1\n- **User Stories Relacionadas**: US-10\n\n---\n\n## 2. Objetivo da Task\nAtualizar as queries SQLC existentes para incluir a coluna `telefone` e criar a nova query `UpdateUser`. Apos editar, executar `make generate` para regenerar o codigo Go.\n\n---\n\n## 3. Descricao Detalhada\n\n### 3.1 Atualizar query `CreateUser`\nNo arquivo `internal/db/sqlc/queries/user.sql`, modificar a query `CreateUser` para incluir a coluna `telefone`:\n\n```sql\n-- name: CreateUser :one\nINSERT INTO usuarios (id, nome, email, senha_hash, telefone, data_criacao, data_atualizacao)\nVALUES (?, ?, ?, ?, ?, ?, ?)\nRETURNING *;\n```\n\n### 3.2 Criar query `UpdateUser`\nAdicionar nova query `UpdateUser` ao arquivo `internal/db/sqlc/queries/user.sql`:\n\n```sql\n-- name: UpdateUser :one\nUPDATE usuarios SET nome = ?, email = ?, telefone = ?, data_atualizacao = ?\nWHERE id = ?\nRETURNING *;\n```\n\n### 3.3 Query `GetUserByID`\nA query `GetUserByID` usa `SELECT *`, portanto ja retornara automaticamente a coluna `telefone`. NAO precisa ser alterada.\n\n### 3.4 Regenerar codigo\nExecutar `make generate` (ou `sqlc generate` dentro de `internal/db/sqlc/`) para regenerar os arquivos Go em `internal/db/sqlc/`.\n\n---\n\n## 4. Aceite Tecnico (criterios objetivos)\nA task estara concluida quando:\n- [ ] Query `CreateUser` inclui coluna `telefone`\n- [ ] Query `UpdateUser` criada com colunas `nome`, `email`, `telefone`, `data_atualizacao` e filtro por `id`\n- [ ] Query `GetUserByID` mantida inalterada (ja usa SELECT *)\n- [ ] `make generate` executado com sucesso\n- [ ] Codigo gerado em `internal/db/sqlc/` reflete as novas queries\n- [ ] Struct `CreateUserParams` gerada inclui campo `Telefone`\n- [ ] Struct `UpdateUserParams` gerada existe com campos corretos\n- [ ] Struct `Usuario` gerada inclui campo `Telefone`\n\n---\n\n## 5. Arquivos Impactados\n\n### 5.1 Arquivos a Criar\n| Arquivo | Descricao |\n|---------|-----------|\n| Nenhum | N/A |\n\n### 5.2 Arquivos a Modificar\n| Arquivo | Modificacao |\n|---------|------------|\n| `internal/db/sqlc/queries/user.sql` | Adicionar coluna `telefone` ao CreateUser e criar query UpdateUser |\n\n### 5.3 Arquivos de Referencia\n| Arquivo | Motivo da Consulta |\n|---------|-------------------|\n| `internal/db/sqlc/sqlc.yaml` | Configuracao do SQLC para verificar engine e paths |\n| `internal/db/migrations/002_add_telefone_usuarios.sql` | Schema atualizado com coluna telefone |\n\n---\n\n## 6. Testes\n\n### 6.1 Testes Unitarios\nN/A -- queries SQLC sao validadas na geracao e testadas via integracao.\n\n### 6.2 Testes de Integracao\n- [ ] Teste: Query `CreateUser` com campo `telefone` preenchido insere registro corretamente e retorna todos os campos incluindo `telefone`\n- [ ] Teste: Query `CreateUser` com campo `telefone` vazio (NULL) insere registro corretamente\n- [ ] Teste: Query `UpdateUser` atualiza `nome`, `email`, `telefone` e `data_atualizacao` corretamente\n- [ ] Teste: Query `UpdateUser` com `telefone` NULL atualiza corretamente\n- [ ] Teste: Query `GetUserByID` retorna campo `telefone` corretamente\n\n### 6.3 Testes E2E\nN/A -- coberto pelos testes de integracao e camadas superiores.\n\n### 6.4 Cenarios de Erro\n- [ ] Cenario: `UpdateUser` com ID inexistente retorna erro (sql.ErrNoRows)\n\n---\n\n## 7. Notas / Observacoes\n- NAO editar arquivos em `internal/db/sqlc/` diretamente (sao gerados)\n- Apenas editar `internal/db/sqlc/queries/user.sql`\n- Apos editar, obrigatoriamente rodar `make generate`\n- O campo `telefone` no SQLite sera do tipo `sql.NullString` no codigo gerado (por ser nullable)\n\n---\n\n## 8. Checklist Final\n- [ ] Implementada conforme SPEC\n- [ ] Testes unitarios criados/atualizados\n- [ ] Testes de integracao criados/atualizados\n- [ ] Aceite tecnico atendido\n- [ ] Revisada\n- [ ] Integrada a branch principal\n\n---\n---\n\n# T3 -- Atualizar repository com campo Phone e metodo Update\n\n## 1. Identificacao\n- **ID**: T3\n- **Nome da Task**: Atualizar repository com campo Phone e metodo Update\n- **Responsavel**: N/A\n- **Status**: A Fazer\n- **Fase**: 2 -- Implementacao Backend\n- **Dependencias**: T2\n- **User Stories Relacionadas**: US-10\n\n---\n\n## 2. Objetivo da Task\nAdicionar campo `Phone` ao modelo `User` do repository, atualizar o mapeamento SQLC, adicionar metodo `Update` a interface `UserRepository` e implementa-lo.\n\n---\n\n## 3. Descricao Detalhada\n\n### 3.1 Atualizar struct `User`\nEm `internal/repository/user_repository.go`, adicionar campo `Phone` a struct `User`:\n\n```go\ntype User struct {\n ID string\n Name string\n Email string\n PasswordHash string\n Phone string // mapeado de 'telefone' no banco\n CreatedAt time.Time\n UpdatedAt time.Time\n}\n```\n\n### 3.2 Atualizar interface `UserRepository`\nAdicionar metodo `Update` a interface:\n\n```go\ntype UserRepository interface {\n Create(ctx context.Context, user *User) (*User, error)\n GetByID(ctx context.Context, id string) (*User, error)\n Update(ctx context.Context, user *User) (*User, error)\n}\n```\n\n### 3.3 Atualizar metodo `Create`\nIncluir campo `Telefone` nos params do SQLC:\n\n```go\nparams := sqlc.CreateUserParams{\n // ... campos existentes ...\n Telefone: sql.NullString{String: user.Phone, Valid: user.Phone != \"\"},\n}\n```\n\n### 3.4 Implementar metodo `Update`\nCriar metodo `Update` na struct `sqlcUserRepository`:\n\n```go\nfunc (r *sqlcUserRepository) Update(ctx context.Context, user *User) (*User, error) {\n params := sqlc.UpdateUserParams{\n Nome: user.Name,\n Email: user.Email,\n Telefone: sql.NullString{String: user.Phone, Valid: user.Phone != \"\"},\n DataAtualizacao: user.UpdatedAt,\n ID: user.ID,\n }\n row, err := r.queries.UpdateUser(ctx, params)\n if err != nil {\n if err == sql.ErrNoRows {\n return nil, fmt.Errorf(\"repository: usuario com id %q nao encontrado\", user.ID)\n }\n return nil, fmt.Errorf(\"repository: falha ao atualizar usuario: %w\", err)\n }\n return toUser(row), nil\n}\n```\n\n### 3.5 Atualizar funcao `toUser`\nIncluir mapeamento do campo `Telefone`:\n\n```go\nfunc toUser(u sqlc.Usuario) *User {\n return &User{\n // ... campos existentes ...\n Phone: u.Telefone.String, // NullString -> string\n }\n}\n```\n\n---\n\n## 4. Aceite Tecnico (criterios objetivos)\nA task estara concluida quando:\n- [ ] Struct `User` possui campo `Phone string`\n- [ ] Interface `UserRepository` possui metodo `Update(ctx, *User) (*User, error)`\n- [ ] Metodo `Create` envia campo `Telefone` ao SQLC\n- [ ] Metodo `Update` implementado com mapeamento correto\n- [ ] Funcao `toUser` mapeia `Telefone` -> `Phone`\n- [ ] Tratamento de `sql.NullString` para campo nullable\n- [ ] Erros encapsulados com mensagens em portugues\n- [ ] Nenhuma quebra nos fluxos existentes\n\n---\n\n## 5. Arquivos Impactados\n\n### 5.1 Arquivos a Criar\n| Arquivo | Descricao |\n|---------|-----------|\n| Nenhum | N/A |\n\n### 5.2 Arquivos a Modificar\n| Arquivo | Modificacao |\n|---------|------------|\n| `internal/repository/user_repository.go` | Adicionar campo Phone ao User, metodo Update a interface e implementacao, atualizar Create e toUser |\n\n### 5.3 Arquivos de Referencia\n| Arquivo | Motivo da Consulta |\n|---------|-------------------|\n| `internal/db/sqlc/` (codigo gerado) | Verificar structs e metodos gerados pelo SQLC |\n\n---\n\n## 6. Testes\n\n### 6.1 Testes Unitarios\nN/A -- repository e testado via integracao (depende do banco).\n\n### 6.2 Testes de Integracao\n- [ ] Teste: `Create` com campo `Phone` preenchido persiste e retorna corretamente\n- [ ] Teste: `Create` com campo `Phone` vazio persiste com `telefone` NULL no banco\n- [ ] Teste: `Update` com campo `Phone` preenchido atualiza e retorna corretamente\n- [ ] Teste: `Update` com campo `Phone` vazio (limpar telefone) atualiza corretamente\n- [ ] Teste: `GetByID` retorna campo `Phone` corretamente\n- [ ] Teste: `Update` com ID inexistente retorna erro \"usuario nao encontrado\"\n\n### 6.3 Testes E2E\nN/A -- coberto pelos testes de integracao e testes do service.\n\n### 6.4 Cenarios de Erro\n- [ ] Cenario: `Update` com ID inexistente retorna erro adequado\n- [ ] Cenario: `Update` com email duplicado retorna erro de constraint\n\n---\n\n## 7. Notas / Observacoes\n- O campo `telefone` no banco e nullable, portanto usar `sql.NullString` nos params SQLC\n- Na conversao de volta (`toUser`), converter `NullString.String` para string simples (vazio se NULL)\n- O import de `database/sql` ja existe no arquivo\n\n---\n\n## 8. Checklist Final\n- [ ] Implementada conforme SPEC\n- [ ] Testes unitarios criados/atualizados\n- [ ] Testes de integracao criados/atualizados\n- [ ] Aceite tecnico atendido\n- [ ] Revisada\n- [ ] Integrada a branch principal\n\n---\n---\n\n# T4 -- Atualizar service com campo Phone, validacao e UpdateUser\n\n## 1. Identificacao\n- **ID**: T4\n- **Nome da Task**: Atualizar service com campo Phone, validacao e UpdateUser\n- **Responsavel**: N/A\n- **Status**: A Fazer\n- **Fase**: 2 -- Implementacao Backend\n- **Dependencias**: T3\n- **User Stories Relacionadas**: US-10\n\n---\n\n## 2. Objetivo da Task\nAdicionar campo `Phone` ao `CreateUserInput`, criar `UpdateUserInput`, adicionar metodo `UpdateUser` a interface `UserService`, implementar validacao de telefone (10-15 caracteres quando informado) e implementar o metodo `UpdateUser`.\n\n---\n\n## 3. Descricao Detalhada\n\n### 3.1 Atualizar `CreateUserInput`\nAdicionar campo `Phone` ao input de criacao:\n\n```go\ntype CreateUserInput struct {\n Name string\n Email string\n Password string\n Phone string // opcional\n}\n```\n\n### 3.2 Criar `UpdateUserInput`\nCriar nova struct de input para atualizacao:\n\n```go\ntype UpdateUserInput struct {\n ID string\n Name string\n Email string\n Phone string // opcional\n}\n```\n\n### 3.3 Criar variavel de erro para telefone\n```go\nvar ErrInvalidPhone = errors.New(\"telefone deve ter entre 10 e 15 caracteres\")\n```\n\n### 3.4 Atualizar interface `UserService`\nAdicionar metodo `UpdateUser`:\n\n```go\ntype UserService interface {\n CreateUser(ctx context.Context, input CreateUserInput) (*repository.User, error)\n GetUser(ctx context.Context, id string) (*repository.User, error)\n UpdateUser(ctx context.Context, input UpdateUserInput) (*repository.User, error)\n}\n```\n\n### 3.5 Atualizar metodo `CreateUser`\nAdicionar validacao de telefone e passar campo Phone ao repository:\n\n```go\nfunc (s *userServiceImpl) CreateUser(ctx context.Context, input CreateUserInput) (*repository.User, error) {\n // ... validacoes existentes ...\n\n // Validacao de telefone (opcional)\n if input.Phone != \"\" {\n if len(input.Phone) < 10 || len(input.Phone) > 15 {\n return nil, ErrInvalidPhone\n }\n }\n\n // ... hash de senha ...\n\n user := &repository.User{\n // ... campos existentes ...\n Phone: input.Phone,\n }\n return s.repo.Create(ctx, user)\n}\n```\n\n### 3.6 Implementar metodo `UpdateUser`\n```go\nfunc (s *userServiceImpl) UpdateUser(ctx context.Context, input UpdateUserInput) (*repository.User, error) {\n if input.ID == \"\" {\n return nil, errors.New(\"id e obrigatorio\")\n }\n if input.Name == \"\" {\n return nil, errors.New(\"nome e obrigatorio\")\n }\n if input.Email == \"\" {\n return nil, errors.New(\"email e obrigatorio\")\n }\n if input.Phone != \"\" {\n if len(input.Phone) < 10 || len(input.Phone) > 15 {\n return nil, ErrInvalidPhone\n }\n }\n\n now := time.Now()\n user := &repository.User{\n ID: input.ID,\n Name: input.Name,\n Email: input.Email,\n Phone: input.Phone,\n UpdatedAt: now,\n }\n return s.repo.Update(ctx, user)\n}\n```\n\n---\n\n## 4. Aceite Tecnico (criterios objetivos)\nA task estara concluida quando:\n- [ ] `CreateUserInput` possui campo `Phone`\n- [ ] `UpdateUserInput` criado com campos `ID`, `Name`, `Email`, `Phone`\n- [ ] Interface `UserService` possui metodo `UpdateUser`\n- [ ] Validacao de telefone: se informado, entre 10 e 15 caracteres\n- [ ] Validacao de telefone: se vazio, aceito sem erro (campo opcional)\n- [ ] `ErrInvalidPhone` definido como variavel de erro exportada\n- [ ] `CreateUser` passa campo `Phone` ao repository\n- [ ] `UpdateUser` implementado com validacoes de nome, email, id e phone\n- [ ] Erros com mensagens em portugues\n- [ ] Nenhuma quebra nos fluxos existentes\n\n---\n\n## 5. Arquivos Impactados\n\n### 5.1 Arquivos a Criar\n| Arquivo | Descricao |\n|---------|-----------|\n| Nenhum | N/A |\n\n### 5.2 Arquivos a Modificar\n| Arquivo | Modificacao |\n|---------|------------|\n| `internal/service/user_service.go` | Adicionar Phone ao CreateUserInput, criar UpdateUserInput, UpdateUser, validacao de telefone |\n\n### 5.3 Arquivos de Referencia\n| Arquivo | Motivo da Consulta |\n|---------|-------------------|\n| `internal/repository/user_repository.go` | Interface UserRepository e struct User atualizadas |\n\n---\n\n## 6. Testes\n\n### 6.1 Testes Unitarios\n- [ ] Teste: `CreateUser` com phone valido (10 caracteres) cria usuario com sucesso\n- [ ] Teste: `CreateUser` com phone valido (15 caracteres) cria usuario com sucesso\n- [ ] Teste: `CreateUser` com phone vazio cria usuario com sucesso (campo opcional)\n- [ ] Teste: `CreateUser` com phone invalido (9 caracteres) retorna `ErrInvalidPhone`\n- [ ] Teste: `CreateUser` com phone invalido (16 caracteres) retorna `ErrInvalidPhone`\n- [ ] Teste: `UpdateUser` com dados validos atualiza usuario com sucesso\n- [ ] Teste: `UpdateUser` com phone valido atualiza com sucesso\n- [ ] Teste: `UpdateUser` com phone vazio atualiza com sucesso\n- [ ] Teste: `UpdateUser` com phone invalido retorna `ErrInvalidPhone`\n- [ ] Teste: `UpdateUser` com ID vazio retorna erro\n- [ ] Teste: `UpdateUser` com nome vazio retorna erro\n- [ ] Teste: `UpdateUser` com email vazio retorna erro\n- [ ] Teste: `UpdateUser` com erro do repository propaga erro\n\n### 6.2 Testes de Integracao\nN/A -- service e testado com mocks do repository (unitarios).\n\n### 6.3 Testes E2E\nN/A -- coberto pelos testes do handler.\n\n### 6.4 Cenarios de Erro\n- [ ] Cenario: Validacao de telefone com caracteres no limite inferior (10) e superior (15) -- boundary test\n- [ ] Cenario: Repository retorna erro na atualizacao (ex: usuario nao encontrado)\n\n### Testes existentes a modificar\n| Arquivo | Modificacao |\n|---------|------------|\n| `internal/service/user_service_test.go` | Atualizar mock `MockUserRepository` para incluir metodo `Update`. Atualizar testes de `CreateUser` para incluir campo `Phone` no `CreateUserInput`. Adicionar novos testes para `UpdateUser` e validacao de telefone. |\n\n---\n\n## 7. Notas / Observacoes\n- O mock `MockUserRepository` no arquivo de testes precisa ser atualizado para incluir o metodo `Update`\n- Os testes existentes de `CreateUser` continuam validos, pois `Phone` e opcional (zero value \"\" e aceito)\n- Padrao table-driven deve ser usado para testes de validacao de telefone (conforme padrao existente)\n\n---\n\n## 8. Checklist Final\n- [ ] Implementada conforme SPEC\n- [ ] Testes unitarios criados/atualizados\n- [ ] Testes de integracao criados/atualizados\n- [ ] Aceite tecnico atendido\n- [ ] Revisada\n- [ ] Integrada a branch principal\n\n---\n---\n\n# T5 -- Atualizar proto com campo phone e endpoint UpdateUser\n\n## 1. Identificacao\n- **ID**: T5\n- **Nome da Task**: Atualizar proto com campo phone e endpoint UpdateUser\n- **Responsavel**: N/A\n- **Status**: A Fazer\n- **Fase**: 3 -- Interface gRPC\n- **Dependencias**: Nenhuma\n- **User Stories Relacionadas**: US-10\n\n---\n\n## 2. Objetivo da Task\nAtualizar o arquivo `api/proto/v1/user.proto` para adicionar campo `phone` nas mensagens existentes, criar mensagens `UpdateUserRequest`/`UpdateUserResponse` e adicionar RPC `UpdateUser` ao service. Executar `make generate` para regenerar codigo.\n\n---\n\n## 3. Descricao Detalhada\n\n### 3.1 Adicionar campo `phone` ao `CreateUserRequest`\n```proto\nmessage CreateUserRequest {\n string name = 1;\n string email = 2;\n string password = 3;\n string phone = 4; // opcional\n}\n```\n\n### 3.2 Adicionar campo `phone` ao `CreateUserResponse`\n```proto\nmessage CreateUserResponse {\n string id = 1;\n string name = 2;\n string email = 3;\n string created_at = 4;\n string phone = 5;\n}\n```\n\n### 3.3 Adicionar campo `phone` ao `GetUserResponse`\n```proto\nmessage GetUserResponse {\n string id = 1;\n string name = 2;\n string email = 3;\n string created_at = 4;\n string updated_at = 5;\n string phone = 6;\n}\n```\n\n### 3.4 Criar mensagens `UpdateUserRequest` e `UpdateUserResponse`\n```proto\nmessage UpdateUserRequest {\n string id = 1;\n string name = 2;\n string email = 3;\n string phone = 4;\n}\n\nmessage UpdateUserResponse {\n string id = 1;\n string name = 2;\n string email = 3;\n string phone = 4;\n string created_at = 5;\n string updated_at = 6;\n}\n```\n\n### 3.5 Adicionar RPC `UpdateUser` ao service\n```proto\nservice UserService {\n rpc CreateUser(CreateUserRequest) returns (CreateUserResponse);\n rpc GetUser(GetUserRequest) returns (GetUserResponse);\n rpc UpdateUser(UpdateUserRequest) returns (UpdateUserResponse);\n}\n```\n\n### 3.6 Regenerar codigo\nExecutar `make generate` (buf generate) para regenerar `gen/proto/v1/`.\n\n---\n\n## 4. Aceite Tecnico (criterios objetivos)\nA task estara concluida quando:\n- [ ] Campo `phone` adicionado a `CreateUserRequest` (field number 4)\n- [ ] Campo `phone` adicionado a `CreateUserResponse` (field number 5)\n- [ ] Campo `phone` adicionado a `GetUserResponse` (field number 6)\n- [ ] Mensagens `UpdateUserRequest` e `UpdateUserResponse` criadas\n- [ ] RPC `UpdateUser` adicionado ao `UserService`\n- [ ] `make generate` executado com sucesso\n- [ ] Codigo gerado em `gen/proto/v1/` reflete as novas mensagens e RPC\n- [ ] Numeracao de campos nao reutiliza numeros existentes\n\n---\n\n## 5. Arquivos Impactados\n\n### 5.1 Arquivos a Criar\n| Arquivo | Descricao |\n|---------|-----------|\n| Nenhum | N/A |\n\n### 5.2 Arquivos a Modificar\n| Arquivo | Modificacao |\n|---------|------------|\n| `api/proto/v1/user.proto` | Adicionar campo phone em messages existentes, criar UpdateUser request/response, adicionar RPC UpdateUser |\n\n### 5.3 Arquivos de Referencia\n| Arquivo | Motivo da Consulta |\n|---------|-------------------|\n| `buf.yaml` | Verificar regras de lint |\n| `buf.gen.yaml` | Verificar plugins de geracao |\n\n---\n\n## 6. Testes\n\n### 6.1 Testes Unitarios\nN/A -- arquivo proto nao possui testes unitarios. Validacao e feita pelo buf lint.\n\n### 6.2 Testes de Integracao\nN/A -- coberto pelos testes do handler.\n\n### 6.3 Testes E2E\nN/A -- coberto pelos testes do handler.\n\n### 6.4 Cenarios de Erro\n- [ ] Cenario: `buf lint` passa sem erros apos modificacoes\n- [ ] Cenario: `make generate` compila sem erros\n\n---\n\n## 7. Notas / Observacoes\n- Esta task pode rodar em paralelo com T1 (sao independentes)\n- NAO editar arquivos em `gen/` diretamente\n- Apenas editar `api/proto/v1/user.proto`\n- Apos editar, obrigatoriamente rodar `make generate`\n- Field numbers novos devem seguir sequencia existente sem reutilizar numeros\n\n---\n\n## 8. Checklist Final\n- [ ] Implementada conforme SPEC\n- [ ] Testes unitarios criados/atualizados\n- [ ] Testes de integracao criados/atualizados\n- [ ] Aceite tecnico atendido\n- [ ] Revisada\n- [ ] Integrada a branch principal\n\n---\n---\n\n# T6 -- Atualizar handler com campo phone e endpoint UpdateUser\n\n## 1. Identificacao\n- **ID**: T6\n- **Nome da Task**: Atualizar handler com campo phone e endpoint UpdateUser\n- **Responsavel**: N/A\n- **Status**: A Fazer\n- **Fase**: 3 -- Interface gRPC\n- **Dependencias**: T4, T5\n- **User Stories Relacionadas**: US-10\n\n---\n\n## 2. Objetivo da Task\nAtualizar o handler gRPC `CreateUser` para incluir campo `phone`, implementar handler `UpdateUser`, atualizar `GetUser` para retornar `phone`, e atualizar o mapeamento de erros para incluir validacao de telefone. Registrar o endpoint `UpdateUser` no servidor gRPC.\n\n---\n\n## 3. Descricao Detalhada\n\n### 3.1 Atualizar handler `CreateUser`\nEm `internal/handler/grpc/user_handler.go`, incluir campo `Phone` no input e na response:\n\n```go\nfunc (h *UserHandler) CreateUser(ctx context.Context, req *pb.CreateUserRequest) (*pb.CreateUserResponse, error) {\n input := service.CreateUserInput{\n Name: req.GetName(),\n Email: req.GetEmail(),\n Password: req.GetPassword(),\n Phone: req.GetPhone(),\n }\n user, err := h.userService.CreateUser(ctx, input)\n if err != nil {\n return nil, mapServiceError(err)\n }\n return &pb.CreateUserResponse{\n Id: user.ID,\n Name: user.Name,\n Email: user.Email,\n Phone: user.Phone,\n CreatedAt: user.CreatedAt.Format(time.RFC3339),\n }, nil\n}\n```\n\n### 3.2 Atualizar handler `GetUser`\nIncluir campo `Phone` na response:\n\n```go\nreturn &pb.GetUserResponse{\n // ... campos existentes ...\n Phone: user.Phone,\n}, nil\n```\n\n### 3.3 Implementar handler `UpdateUser`\n```go\nfunc (h *UserHandler) UpdateUser(ctx context.Context, req *pb.UpdateUserRequest) (*pb.UpdateUserResponse, error) {\n input := service.UpdateUserInput{\n ID: req.GetId(),\n Name: req.GetName(),\n Email: req.GetEmail(),\n Phone: req.GetPhone(),\n }\n user, err := h.userService.UpdateUser(ctx, input)\n if err != nil {\n return nil, mapServiceError(err)\n }\n return &pb.UpdateUserResponse{\n Id: user.ID,\n Name: user.Name,\n Email: user.Email,\n Phone: user.Phone,\n CreatedAt: user.CreatedAt.Format(time.RFC3339),\n UpdatedAt: user.UpdatedAt.Format(time.RFC3339),\n }, nil\n}\n```\n\n### 3.4 Atualizar `mapServiceError`\nAdicionar mapeamento para erro de telefone invalido (ja coberto pelo pattern \"obrigatorio\" existente, mas verificar se \"deve ter entre\" precisa de tratamento):\n\nO erro `ErrInvalidPhone` (\"telefone deve ter entre 10 e 15 caracteres\") deve ser mapeado para `codes.InvalidArgument`. Verificar se o pattern existente no `mapServiceError` cobre este caso. Se nao cobrir, adicionar verificacao.\n\n### 3.5 Configurar autenticacao\nSe `UpdateUser` deve ser protegido (requer autenticacao), NAO adicionar a `skip_methods` em `configs/config.yaml`. Se deve ser publico, adicionar. Conforme o SPEC_TECH, nao ha indicacao de que deva ser publico, entao sera protegido por padrao.\n\n---\n\n## 4. Aceite Tecnico (criterios objetivos)\nA task estara concluida quando:\n- [ ] Handler `CreateUser` passa campo `Phone` ao service e retorna na response\n- [ ] Handler `GetUser` retorna campo `Phone` na response\n- [ ] Handler `UpdateUser` implementado e funcional\n- [ ] Mapeamento de erros atualizado para erro de telefone invalido\n- [ ] Nenhuma quebra nos fluxos existentes\n- [ ] Handler compila corretamente contra o proto gerado\n\n---\n\n## 5. Arquivos Impactados\n\n### 5.1 Arquivos a Criar\n| Arquivo | Descricao |\n|---------|-----------|\n| Nenhum | N/A |\n\n### 5.2 Arquivos a Modificar\n| Arquivo | Modificacao |\n|---------|------------|\n| `internal/handler/grpc/user_handler.go` | Atualizar CreateUser, GetUser e criar UpdateUser com campo phone. Atualizar mapServiceError. |\n\n### 5.3 Arquivos de Referencia\n| Arquivo | Motivo da Consulta |\n|---------|-------------------|\n| `gen/proto/v1/` (codigo gerado) | Verificar interfaces e structs geradas pelo proto |\n| `internal/service/user_service.go` | Interface UserService atualizada |\n| `configs/config.yaml` | Verificar skip_methods para autenticacao |\n\n---\n\n## 6. Testes\n\n### 6.1 Testes Unitarios\n- [ ] Teste: `CreateUser` com phone valido retorna response com campo phone preenchido\n- [ ] Teste: `CreateUser` com phone vazio retorna response com phone vazio\n- [ ] Teste: `CreateUser` com phone invalido retorna `codes.InvalidArgument`\n- [ ] Teste: `GetUser` retorna campo phone na response\n- [ ] Teste: `UpdateUser` com dados validos retorna response correta\n- [ ] Teste: `UpdateUser` com phone valido retorna response com phone preenchido\n- [ ] Teste: `UpdateUser` com phone vazio retorna response com phone vazio\n- [ ] Teste: `UpdateUser` com phone invalido retorna `codes.InvalidArgument`\n- [ ] Teste: `UpdateUser` com ID vazio retorna `codes.InvalidArgument`\n- [ ] Teste: `UpdateUser` com usuario nao encontrado retorna `codes.NotFound`\n- [ ] Teste: `UpdateUser` com erro interno retorna `codes.Internal`\n- [ ] Teste: `UpdateUser` mapeamento de response (todos os campos corretos)\n\n### 6.2 Testes de Integracao\nN/A -- handler e testado com mocks do service (unitarios).\n\n### 6.3 Testes E2E\nN/A -- para esta feature simples, os testes unitarios do handler cobrem o fluxo completo.\n\n### 6.4 Cenarios de Erro\n- [ ] Cenario: Erro de validacao de telefone mapeado corretamente para `codes.InvalidArgument`\n- [ ] Cenario: Erro \"nao encontrado\" do service mapeado para `codes.NotFound`\n- [ ] Cenario: Erro generico do service mapeado para `codes.Internal`\n\n### Testes existentes a modificar\n| Arquivo | Modificacao |\n|---------|------------|\n| `internal/handler/grpc/user_handler_test.go` | Atualizar mock `MockUserService` para incluir metodo `UpdateUser`. Atualizar testes de `CreateUser` para validar campo `Phone` na response. Atualizar testes de `GetUser` para validar campo `Phone` na response. Adicionar novos testes para handler `UpdateUser`. |\n\n---\n\n## 7. Notas / Observacoes\n- O mock `MockUserService` no arquivo de testes precisa ser atualizado para incluir o metodo `UpdateUser`\n- Os testes existentes de `CreateUser` e `GetUser` precisam ser atualizados para incluir assertions do campo `Phone`\n- O handler `UserHandler` ja implementa `UnimplementedUserServiceServer`, entao `UpdateUser` sera automaticamente parte da interface apos regenerar o proto\n- Verificar se `mapServiceError` cobre o novo erro de telefone invalido\n\n---\n\n## 8. Checklist Final\n- [ ] Implementada conforme SPEC\n- [ ] Testes unitarios criados/atualizados\n- [ ] Testes de integracao criados/atualizados\n- [ ] Aceite tecnico atendido\n- [ ] Revisada\n- [ ] Integrada a branch principal\n\n---\n---\n\n## Resumo Final\n\n### Arquivos que seriam salvos (simulacao -- nao salvos em docs/ conforme instrucoes do eval):\n```\ndocs/telefone-usuario/v1/task_plan.md\ndocs/telefone-usuario/v1/tasks/T1.md\ndocs/telefone-usuario/v1/tasks/T2.md\ndocs/telefone-usuario/v1/tasks/T3.md\ndocs/telefone-usuario/v1/tasks/T4.md\ndocs/telefone-usuario/v1/tasks/T5.md\ndocs/telefone-usuario/v1/tasks/T6.md\n```\n\n### Ordem de execucao recomendada:\n```\nParalelo: T1 + T5\n |\n v\n T2 (apos T1)\n |\n v\n T3 (apos T2)\n |\n v\n T4 (apos T3)\n |\n v\n T6 (apos T4 + T5)\n```\n\nTask Plan aprovado para execucao? (sim/nao)\n"}], "grading": {"eval_id": 2, "eval_name": "spec-tech-simples-alteracao-pontual", "configuration": "with_skill", "expectations": [{"text": "Gera um numero proporcional de tasks (entre 3 e 6) \u00e2\u20ac\u201d nao over-engineers com tasks demais para uma alteracao simples", "passed": true, "evidence": "Gerou exatamente 6 tasks (T1-T6), dentro da faixa esperada de 3-6. Proporcional a complexidade da feature."}, {"text": "Respeita a ordem de dependencias das camadas (migracao > SQLC > repository > service > handler > proto)", "passed": true, "evidence": "Dependencias: T1(migracao)->T2(SQLC)->T3(repo)->T4(service)->T6(handler), T5(proto)->T6(handler). Proto em paralelo com migracao. Ordem correta."}, {"text": "Mapeia a US-10 na tabela de rastreabilidade do task_plan", "passed": true, "evidence": "Secao 5 do task_plan contem tabela de rastreabilidade mapeando US-10 para todas as 6 tasks com definicoes tecnicas correspondentes."}, {"text": "Cada task segue o template completo (secoes 1-8) ou marca secoes N/A com justificativa", "passed": true, "evidence": "Todas as 6 tasks (T1-T6) seguem o template com secoes 1-8 preenchidas. Secoes N/A possuem justificativa (ex: T1 secao 6.1: 'N/A -- migracao SQL nao possui testes unitarios isolados')."}, {"text": "Salva task_plan.md como documento de REFERENCIA (sem corpo detalhado de tasks dentro dele)", "passed": true, "evidence": "O task_plan contem apenas identificacao, macro-fases, tabela de tasks com IDs/links, rastreabilidade, dependencias e criterios. Tasks detalhadas estao separadas (T1-T6). Embora tudo esteja em um arquivo de output por ser eval, a estrutura esta correta."}]}}, {"id": "eval-2-spec-simples-without_skill", "prompt": "Preciso de um TASK PLAN para esta feature simples. O SPEC_TECH aprovado esta abaixo:\n\n---\n# SPEC_TECH \u00e2\u20ac\u201d Adicionar Telefone ao Usuario v1\n\n## 1. Identificacao\n| Campo | Valor |\n|-------|-------|\n| **Feature/Projeto** | Adicionar Telefone ao Modulo de Usuario |\n| **Versao** | v1 |\n| **Data** | 2026-03-07 |\n| **PRD** | docs/feature-telefone-usuario/v1/prd.md |\n\n## 2. Resumo Tecnico\nAdicionar campo `telefone` (opcional) a tabela `usuarios` e expor via gRPC nos endpoints CreateUser e UpdateUser.\n\n## 3. Definicoes Tecnicas\n\n### 3.1 Banco de Dados\n- Nova migracao: `ALTER TABLE usuarios ADD COLUMN telefone TEXT;`\n\n### 3.2 SQLC\n- Atualizar queries CreateUser e UpdateUser para incluir coluna `telefone`\n\n### 3.3 Repository\n- Adicionar campo `Phone` ao modelo User\n- Mapear `telefone` (banco) para `Phone` (Go)\n\n### 3.4 Service\n- Aceitar campo opcional phone em CreateUser e UpdateUser\n- Validacao: se informado, deve ter entre 10 e 15 caracteres\n\n### 3.5 Handler gRPC\n- Adicionar campo `phone` nas mensagens proto CreateUserRequest, UpdateUserRequest e User\n\n### 3.6 Proto\n- Atualizar user.proto com campo `string phone = X;`\n\n## 4. User Stories Relacionadas\n- US-10: Como usuario, quero informar meu telefone para receber notificacoes por SMS\n---", "eval_id": 2, "outputs": [{"name": "response.md", "type": "text", "content": "# TASK PLAN \u00e2\u20ac\u201d Adicionar Telefone ao Usuario v1\n\n## 1. Identificacao\n\n| Campo | Valor |\n|-------|-------|\n| **Feature** | Adicionar Telefone ao Modulo de Usuario |\n| **Versao** | v1 |\n| **Data** | 2026-03-07 |\n| **SPEC_TECH** | docs/feature-telefone-usuario/v1/spec_tech.md |\n| **Branch base** | user-feature |\n\n## 2. Resumo\n\nAdicionar o campo opcional `telefone` na tabela `usuarios` e propagar a mudanca por todas as camadas: migracao SQL, queries SQLC, repository, service, proto e handler gRPC. Inclui validacao opcional (10-15 caracteres quando informado) e testes unitarios.\n\n## 3. Tarefas\n\n### TASK-1: Criar migracao SQL para adicionar coluna telefone\n\n**Camada:** Banco de Dados\n**Arquivo a criar:** `internal/db/migrations/002_add_telefone_usuarios.sql`\n**Descricao:**\nCriar nova migracao com `ALTER TABLE usuarios ADD COLUMN telefone TEXT;`. Conforme regra de imutabilidade, nao editar a migracao 001 existente.\n\n**Conteudo esperado:**\n```sql\nALTER TABLE usuarios ADD COLUMN telefone TEXT;\n```\n\n**Criterio de aceite:**\n- Arquivo criado em `internal/db/migrations/002_add_telefone_usuarios.sql`\n- Migracao 001 inalterada\n- Coluna `telefone` do tipo `TEXT` (nullable/opcional)\n\n**Dependencias:** Nenhuma\n\n---\n\n### TASK-2: Atualizar queries SQLC para incluir telefone\n\n**Camada:** SQLC Queries\n**Arquivo a editar:** `internal/db/sqlc/queries/user.sql`\n**Descricao:**\nAdicionar a coluna `telefone` na query `CreateUser` (INSERT e parametros) e criar uma nova query `UpdateUser` que permita atualizar nome e telefone.\n\n**Alteracoes esperadas:**\n\nQuery `CreateUser` atualizada:\n```sql\n-- name: CreateUser :one\nINSERT INTO usuarios (id, nome, email, senha_hash, telefone, data_criacao, data_atualizacao)\nVALUES (?, ?, ?, ?, ?, ?, ?)\nRETURNING *;\n```\n\nNova query `UpdateUser`:\n```sql\n-- name: UpdateUser :one\nUPDATE usuarios SET nome = ?, telefone = ?, data_atualizacao = ?\nWHERE id = ?\nRETURNING *;\n```\n\n**Criterio de aceite:**\n- Query `CreateUser` inclui coluna `telefone`\n- Nova query `UpdateUser` criada\n- Executar `make generate` (ou `make sqlc`) apos a edicao para regenerar codigo\n\n**Dependencias:** TASK-1\n\n---\n\n### TASK-3: Regenerar codigo SQLC\n\n**Camada:** Codigo Gerado\n**Descricao:**\nExecutar `make generate` para que o SQLC regenere os arquivos Go em `internal/db/sqlc/` com a nova coluna `telefone` e a nova query `UpdateUser`.\n\n**Comando:**\n```bash\nmake generate\n```\n\n**Criterio de aceite:**\n- Struct `Usuario` gerada inclui campo `Telefone`\n- Struct `CreateUserParams` inclui campo `Telefone`\n- Struct `UpdateUserParams` gerada\n- Funcao `UpdateUser` gerada em queries\n\n**Dependencias:** TASK-1, TASK-2\n\n---\n\n### TASK-4: Atualizar modelo e repository do usuario\n\n**Camada:** Repository\n**Arquivo a editar:** `internal/repository/user_repository.go`\n**Descricao:**\nAdicionar campo `Phone` ao modelo `User`. Atualizar o mapeamento na funcao `toUser` para converter `telefone` (banco) para `Phone` (Go). Atualizar `Create` para passar `telefone` nos parametros SQLC. Adicionar metodo `Update` na interface e implementacao.\n\n**Alteracoes esperadas:**\n\n1. Adicionar campo na struct:\n```go\ntype User struct {\n ID string\n Name string\n Email string\n PasswordHash string\n Phone string // novo campo\n CreatedAt time.Time\n UpdatedAt time.Time\n}\n```\n\n2. Atualizar interface:\n```go\ntype UserRepository interface {\n Create(ctx context.Context, user *User) (*User, error)\n GetByID(ctx context.Context, id string) (*User, error)\n Update(ctx context.Context, user *User) (*User, error) // novo metodo\n}\n```\n\n3. Atualizar `Create` para incluir `Telefone` nos params SQLC\n4. Implementar metodo `Update` usando `r.queries.UpdateUser`\n5. Atualizar funcao `toUser` para mapear `Telefone` -> `Phone`\n\n**Criterio de aceite:**\n- Campo `Phone` presente no modelo `User`\n- Mapeamento `Telefone` <-> `Phone` funcional em `toUser`\n- Metodo `Update` implementado\n- `Create` passa `telefone` para o SQLC\n\n**Dependencias:** TASK-3\n\n---\n\n### TASK-5: Atualizar service do usuario\n\n**Camada:** Service\n**Arquivo a editar:** `internal/service/user_service.go`\n**Descricao:**\nAdicionar campo `Phone` ao `CreateUserInput`. Criar struct `UpdateUserInput`. Adicionar metodo `UpdateUser` na interface e implementacao. Implementar validacao: se `phone` informado, deve ter entre 10 e 15 caracteres.\n\n**Alteracoes esperadas:**\n\n1. Atualizar input de criacao:\n```go\ntype CreateUserInput struct {\n Name string\n Email string\n Password string\n Phone string // novo, opcional\n}\n```\n\n2. Criar input de atualizacao:\n```go\ntype UpdateUserInput struct {\n ID string\n Name string\n Phone string // opcional\n}\n```\n\n3. Atualizar interface:\n```go\ntype UserService interface {\n CreateUser(ctx context.Context, input CreateUserInput) (*repository.User, error)\n GetUser(ctx context.Context, id string) (*repository.User, error)\n UpdateUser(ctx context.Context, input UpdateUserInput) (*repository.User, error) // novo\n}\n```\n\n4. Adicionar validacao de telefone (reutilizavel em Create e Update):\n```go\nfunc validatePhone(phone string) error {\n if phone != \"\" && (len(phone) < 10 || len(phone) > 15) {\n return errors.New(\"telefone deve ter entre 10 e 15 caracteres\")\n }\n return nil\n}\n```\n\n5. Chamar `validatePhone` em `CreateUser` e `UpdateUser`\n6. Passar `Phone` para `repository.User` em `CreateUser`\n7. Implementar `UpdateUser` com busca por ID + chamada ao repo\n\n**Criterio de aceite:**\n- Campo `Phone` aceito em `CreateUser`\n- Metodo `UpdateUser` implementado\n- Validacao de telefone (10-15 chars) funcional\n- Telefone vazio e aceito (campo opcional)\n\n**Dependencias:** TASK-4\n\n---\n\n### TASK-6: Atualizar definicao Proto\n\n**Camada:** Proto/gRPC\n**Arquivo a editar:** `api/proto/v1/user.proto`\n**Descricao:**\nAdicionar campo `string phone` nas mensagens `CreateUserRequest`, `CreateUserResponse`, `GetUserResponse`. Criar mensagens `UpdateUserRequest` e `UpdateUserResponse`. Adicionar RPC `UpdateUser` ao service.\n\n**Alteracoes esperadas:**\n\n1. Adicionar `string phone = 4;` em `CreateUserRequest`\n2. Adicionar `string phone = 4;` em `CreateUserResponse` (apos `email`, ajustar numeracao de `created_at` para 5)\n3. Adicionar `string phone = 4;` em `GetUserResponse` (apos `email`, ajustar numeracao: `phone=4`, `created_at=5`, `updated_at=6`)\n\n> **ATENCAO:** A numeracao dos campos existentes NAO pode ser alterada sem quebrar compatibilidade. Verificar a numeracao atual antes de definir os novos numeros. Dado que `CreateUserResponse` ja usa campos 1-4 e `GetUserResponse` usa 1-5, o campo `phone` deve usar o proximo numero disponivel em cada mensagem.\n\nNumeracao correta baseada no proto atual:\n- `CreateUserRequest`: `string phone = 4;` (campos existentes: 1=name, 2=email, 3=password)\n- `CreateUserResponse`: `string phone = 5;` (campos existentes: 1=id, 2=name, 3=email, 4=created_at)\n- `GetUserResponse`: `string phone = 6;` (campos existentes: 1=id, 2=name, 3=email, 4=created_at, 5=updated_at)\n\n4. Criar mensagens novas:\n```proto\nmessage UpdateUserRequest {\n string id = 1;\n string name = 2;\n string phone = 3;\n}\n\nmessage UpdateUserResponse {\n string id = 1;\n string name = 2;\n string email = 3;\n string phone = 4;\n string updated_at = 5;\n}\n```\n\n5. Adicionar ao service:\n```proto\nrpc UpdateUser(UpdateUserRequest) returns (UpdateUserResponse);\n```\n\n**Criterio de aceite:**\n- Campo `phone` adicionado nas mensagens existentes com numeracao correta\n- Mensagens `UpdateUserRequest`/`UpdateUserResponse` criadas\n- RPC `UpdateUser` registrado no service\n- Numeracao de campos existentes NAO alterada\n\n**Dependencias:** Nenhuma (pode ser feita em paralelo com TASK-1 a TASK-5)\n\n---\n\n### TASK-7: Regenerar codigo Proto\n\n**Camada:** Codigo Gerado\n**Descricao:**\nExecutar `make generate` (buf generate) para regenerar o codigo Go em `gen/proto/v1/`.\n\n**Comando:**\n```bash\nmake generate\n```\n\n**Criterio de aceite:**\n- Structs Go geradas incluem campo `Phone`\n- Interface `UserServiceServer` inclui metodo `UpdateUser`\n- Compilacao sem erros\n\n**Dependencias:** TASK-6\n\n---\n\n### TASK-8: Atualizar handler gRPC do usuario\n\n**Camada:** Handler\n**Arquivo a editar:** `internal/handler/grpc/user_handler.go`\n**Descricao:**\nAtualizar `CreateUser` para passar `Phone` do request para o service input e incluir `phone` na response. Implementar metodo `UpdateUser`. Atualizar `GetUser` para incluir `phone` na response.\n\n**Alteracoes esperadas:**\n\n1. Em `CreateUser`:\n - Adicionar `Phone: req.GetPhone()` ao `CreateUserInput`\n - Adicionar `Phone: user.Phone` na response\n\n2. Em `GetUser`:\n - Adicionar `Phone: user.Phone` na response\n\n3. Novo metodo `UpdateUser`:\n```go\nfunc (h *UserHandler) UpdateUser(ctx context.Context, req *pb.UpdateUserRequest) (*pb.UpdateUserResponse, error) {\n input := service.UpdateUserInput{\n ID: req.GetId(),\n Name: req.GetName(),\n Phone: req.GetPhone(),\n }\n user, err := h.userService.UpdateUser(ctx, input)\n if err != nil {\n return nil, mapServiceError(err)\n }\n return &pb.UpdateUserResponse{\n Id: user.ID,\n Name: user.Name,\n Email: user.Email,\n Phone: user.Phone,\n UpdatedAt: user.UpdatedAt.Format(time.RFC3339),\n }, nil\n}\n```\n\n**Criterio de aceite:**\n- `CreateUser` repassa e retorna `phone`\n- `GetUser` retorna `phone`\n- `UpdateUser` implementado e funcional\n- Erros mapeados corretamente\n\n**Dependencias:** TASK-5, TASK-7\n\n---\n\n### TASK-9: Configurar UpdateUser como rota protegida\n\n**Camada:** Configuracao\n**Arquivo a verificar:** `configs/config.yaml`\n**Descricao:**\nVerificar que `UpdateUser` NAO esta em `skip_methods` (ou seja, requer autenticacao). Apenas `CreateUser` deve ser publico. Se necessario, nenhuma alteracao e necessaria, apenas confirmar.\n\n**Criterio de aceite:**\n- `UpdateUser` exige autenticacao (Bearer token)\n- Nenhuma rota protegida acidentalmente exposta\n\n**Dependencias:** TASK-8\n\n---\n\n### TASK-10: Atualizar testes unitarios do service\n\n**Camada:** Testes\n**Arquivo a editar:** `internal/service/user_service_test.go`\n**Descricao:**\nAtualizar mock do repository para incluir metodo `Update`. Adicionar testes para:\n\n1. `TestCreateUser_WithPhone_Success` \u00e2\u20ac\u201d criacao com telefone valido\n2. `TestCreateUser_WithInvalidPhone_TooShort` \u00e2\u20ac\u201d telefone com menos de 10 chars\n3. `TestCreateUser_WithInvalidPhone_TooLong` \u00e2\u20ac\u201d telefone com mais de 15 chars\n4. `TestCreateUser_WithEmptyPhone_Success` \u00e2\u20ac\u201d telefone vazio (opcional)\n5. `TestUpdateUser_Success` \u00e2\u20ac\u201d atualizacao com dados validos\n6. `TestUpdateUser_WithPhone_Success` \u00e2\u20ac\u201d atualizacao com telefone valido\n7. `TestUpdateUser_InvalidPhone` \u00e2\u20ac\u201d telefone invalido na atualizacao\n8. `TestUpdateUser_NotFound` \u00e2\u20ac\u201d usuario nao encontrado\n\n**Criterio de aceite:**\n- Mock atualizado com metodo `Update`\n- Todos os cenarios de telefone cobertos (valido, vazio, curto, longo)\n- Testes de `UpdateUser` adicionados\n- Todos os testes passam: `CGO_ENABLED=1 go test ./internal/service/... -v`\n\n**Dependencias:** TASK-5\n\n---\n\n### TASK-11: Atualizar testes unitarios do handler\n\n**Camada:** Testes\n**Arquivo a editar:** `internal/handler/grpc/user_handler_test.go`\n**Descricao:**\nAtualizar mock do service para incluir metodo `UpdateUser`. Adicionar testes para:\n\n1. `TestUserHandler_CreateUser_WithPhone` \u00e2\u20ac\u201d verifica que phone e repassado e retornado\n2. `TestUserHandler_GetUser_WithPhone` \u00e2\u20ac\u201d verifica que phone e retornado\n3. `TestUserHandler_UpdateUser_Success` \u00e2\u20ac\u201d atualizacao com sucesso\n4. `TestUserHandler_UpdateUser_NotFound` \u00e2\u20ac\u201d usuario nao encontrado\n5. `TestUserHandler_UpdateUser_InvalidPhone` \u00e2\u20ac\u201d telefone invalido\n\n**Criterio de aceite:**\n- Mock atualizado com metodo `UpdateUser`\n- Testes de phone no CreateUser e GetUser\n- Testes de UpdateUser cobrindo sucesso e erros\n- Todos os testes passam: `CGO_ENABLED=1 go test ./internal/handler/grpc/... -v`\n\n**Dependencias:** TASK-8\n\n---\n\n### TASK-12: Teste de integracao e validacao final\n\n**Camada:** Validacao\n**Descricao:**\nExecutar build e suite completa de testes para garantir que tudo funciona junto.\n\n**Comandos:**\n```bash\nmake build\nmake test\n```\n\n**Criterio de aceite:**\n- Build compila sem erros\n- Todos os testes passam\n- Nenhuma regressao nos testes existentes\n\n**Dependencias:** TASK-10, TASK-11\n\n---\n\n## 4. Grafo de Dependencias\n\n```\nTASK-1 (migracao SQL)\n \u00e2\u201d\u201d\u00e2\u201d\u20ac> TASK-2 (queries SQLC)\n \u00e2\u201d\u201d\u00e2\u201d\u20ac> TASK-3 (make generate - SQLC)\n \u00e2\u201d\u201d\u00e2\u201d\u20ac> TASK-4 (repository)\n \u00e2\u201d\u201d\u00e2\u201d\u20ac> TASK-5 (service)\n \u00e2\u201d\u0153\u00e2\u201d\u20ac> TASK-8 (handler) [+ TASK-7]\n \u00e2\u201d\u201d\u00e2\u201d\u20ac> TASK-10 (testes service)\n\nTASK-6 (proto) [paralelo com TASK-1..5]\n \u00e2\u201d\u201d\u00e2\u201d\u20ac> TASK-7 (make generate - proto)\n \u00e2\u201d\u201d\u00e2\u201d\u20ac> TASK-8 (handler)\n \u00e2\u201d\u0153\u00e2\u201d\u20ac> TASK-9 (config auth)\n \u00e2\u201d\u201d\u00e2\u201d\u20ac> TASK-11 (testes handler)\n\nTASK-10 + TASK-11\n \u00e2\u201d\u201d\u00e2\u201d\u20ac> TASK-12 (validacao final)\n```\n\n## 5. Ordem de Execucao Sugerida\n\n| Fase | Tasks | Descricao |\n|------|-------|-----------|\n| **Fase 1** | TASK-1, TASK-6 | Migracao SQL + Proto (paralelo) |\n| **Fase 2** | TASK-2 | Queries SQLC |\n| **Fase 3** | TASK-3, TASK-7 | Regenerar codigo (SQLC + Proto) \u00e2\u20ac\u201d executar `make generate` uma unica vez |\n| **Fase 4** | TASK-4 | Repository |\n| **Fase 5** | TASK-5 | Service |\n| **Fase 6** | TASK-8, TASK-9 | Handler + Config |\n| **Fase 7** | TASK-10, TASK-11 | Testes (paralelo) |\n| **Fase 8** | TASK-12 | Validacao final |\n\n## 6. Riscos e Observacoes\n\n1. **Numeracao Proto:** A numeracao dos campos proto existentes NAO pode ser alterada. O campo `phone` deve usar o proximo numero livre em cada mensagem. Isso e critico para compatibilidade.\n\n2. **Migracao imutavel:** A migracao 001 NAO deve ser editada. Sempre criar nova migracao (002).\n\n3. **Codigo gerado:** Nunca editar arquivos em `gen/` ou `internal/db/sqlc/*.go`. Sempre editar os fontes (`.proto`, `.sql`) e rodar `make generate`.\n\n4. **Campo opcional:** O campo `telefone` e `TEXT` nullable no SQLite. No Go, o SQLC pode gerar como `sql.NullString` \u00e2\u20ac\u201d verificar apos geracao e ajustar mapeamento no repository conforme necessario.\n\n5. **UpdateUser e novo:** O proto atual nao tem `UpdateUser`. E necessario criar o RPC, mensagens e implementar em todas as camadas. A SPEC_TECH menciona \"UpdateUser\" mas o codebase atual nao possui esse endpoint.\n\n6. **`make generate`:** Os comandos `make generate` das TASK-3 e TASK-7 podem ser executados em uma unica chamada se o Makefile gerar tanto SQLC quanto Proto.\n"}], "grading": {"eval_id": 2, "eval_name": "spec-tech-simples-alteracao-pontual", "configuration": "without_skill", "expectations": [{"text": "Gera um numero proporcional de tasks (entre 3 e 6) \u00e2\u20ac\u201d nao over-engineers com tasks demais para uma alteracao simples", "passed": false, "evidence": "Gerou 12 tasks para uma feature simples de adicionar um campo. Over-engineered com tasks separadas para 'regenerar codigo SQLC' (TASK-3), 'regenerar codigo Proto' (TASK-7), e 'configurar UpdateUser como rota protegida' (TASK-9) que poderiam ser subtasks."}, {"text": "Respeita a ordem de dependencias das camadas (migracao > SQLC > repository > service > handler > proto)", "passed": true, "evidence": "Grafo de dependencias mostra TASK-1(migracao)->TASK-2(SQLC)->TASK-3(generate)->TASK-4(repo)->TASK-5(service)->TASK-8(handler). Proto em paralelo (TASK-6). Ordem correta."}, {"text": "Mapeia a US-10 na tabela de rastreabilidade do task_plan", "passed": false, "evidence": "Nao ha tabela de rastreabilidade mapeando US-10 para tasks. A US-10 nao e mencionada no task plan."}, {"text": "Cada task segue o template completo (secoes 1-8) ou marca secoes N/A com justificativa", "passed": false, "evidence": "Tasks usam formato ad-hoc (Camada, Arquivo, Descricao, Criterio de aceite, Dependencias) em vez do template oficial com secoes 1-8 (Identificacao, Objetivo, Descricao Detalhada, Aceite Tecnico, Arquivos Impactados, Testes, Notas, Checklist)."}, {"text": "Salva task_plan.md como documento de REFERENCIA (sem corpo detalhado de tasks dentro dele)", "passed": false, "evidence": "Todo o conteudo detalhado das tasks esta inline no mesmo documento. Nao ha separacao entre task_plan (referencia) e tasks individuais."}]}}, {"id": "eval-3-sem-user-stories-with_skill", "prompt": "Gere o TASK PLAN para esta feature. Segue o SPEC_TECH aprovado:\n\n---\n# SPEC_TECH \u00e2\u20ac\u201d Cache de Cardapio v1\n\n## 1. Identificacao\n| Campo | Valor |\n|-------|-------|\n| **Feature/Projeto** | Cache de Cardapio em Memoria |\n| **Versao** | v1 |\n| **Data** | 2026-03-07 |\n\n## 2. Resumo Tecnico\nImplementar cache in-memory (sync.Map) para o cardapio de produtos, evitando queries repetidas ao SQLite. TTL configuravel via config.yaml.\n\n## 3. Definicoes Tecnicas\n\n### 3.1 Novo Pacote\n- `internal/infra/cache/` com interface `Cache[K, V]` e implementacao `InMemoryCache`\n- TTL configuravel via `cache.ttl_seconds` no config.yaml\n- Invalidacao manual via metodo `Invalidate(key)`\n\n### 3.2 Service\n- ProductService passa a consultar cache antes do repository\n- Cache miss: busca no repository e popula cache\n- Operacoes de escrita (Create, Update, Delete) invalidam a entrada correspondente\n\n### 3.3 Config\n- Novo campo `cache.ttl_seconds` (padrao: 300)\n\n### 3.4 DI\n- Registrar modulo cache no FX\n---", "eval_id": 3, "outputs": [{"name": "response.md", "type": "text", "content": "# Resposta do Task Plan Expert \u00e2\u20ac\u201d Interacao Inicial\n\n## 1. Extra\u00c3\u00a7\u00c3\u00a3o do Nome da Feature\n\nIdentifiquei o nome da feature na secao \"1. Identificacao\" do SPEC_TECH:\n\n- **Feature/Projeto**: Cache de Cardapio em Memoria\n- **Nome normalizado (kebab-case)**: `cache-de-cardapio`\n- **Versao**: v1\n- **Diretorio de saida**: `docs/cache-de-cardapio/v1/`\n\n---\n\n## 2. Analise Obrigatoria do Projeto (Codebase)\n\nAntes de planejar qualquer task, explorei o codebase e identifiquei o seguinte:\n\n### O que JA existe no projeto:\n| Elemento | Status | Localizacao |\n|----------|--------|-------------|\n| Estrutura Clean Architecture (handler/service/repository) | Existe | `internal/` |\n| Injecao de dependencia via Uber FX | Existe | `internal/infra/di/fx.go` |\n| Config via Viper + `config.yaml` | Existe | `internal/infra/config/config.go`, `configs/config.yaml` |\n| User module completo (handler, service, repo, tests) | Existe | `internal/handler/grpc/`, `internal/service/`, `internal/repository/` |\n| SQLC para queries tipadas | Existe | `internal/db/sqlc/` |\n| Migracao de usuarios | Existe | `internal/db/migrations/001_create_usuarios.sql` |\n| Testes com testify/mock (table-driven) | Existe | `internal/service/user_service_test.go` |\n| gRPC server com interceptors | Existe | `internal/infra/grpc/server.go` |\n\n### O que NAO existe (precisa ser criado):\n| Elemento | Status |\n|----------|--------|\n| Modulo de Produto (Product) \u00e2\u20ac\u201d service, repository, handler, proto, migration, queries | **NAO existe** |\n| Pacote de cache (`internal/infra/cache/`) | **NAO existe** |\n| Campo `cache.ttl_seconds` no config | **NAO existe** |\n| Tabela `produtos` no banco | **NAO existe** |\n\n### Observacao Critica\n\nO SPEC_TECH fala em \"cache de cardapio de produtos\" e menciona que o `ProductService` passara a consultar o cache antes do repository. Porem, **nao existe nenhum modulo de Produto no projeto**. Nao ha proto, migration, queries SQLC, repository, service ou handler de produtos.\n\nIsso significa que o TASK PLAN precisara incluir tasks para criar o modulo de Produto completo OU o SPEC_TECH assume que esse modulo ja existe/sera criado em outra feature. Essa duvida sera direcionada ao usuario (veja secao 3).\n\n---\n\n## 3. Ponto de Atencao \u00e2\u20ac\u201d User Stories e PRD Ausentes\n\nO SPEC_TECH fornecido **nao contem User Stories (US-XX)** e **nao referencia nenhum PRD**.\n\nNo framework SDD, o TASK PLAN inclui uma **tabela de rastreabilidade obrigatoria** que mapeia User Stories do PRD para definicoes tecnicas do SPEC_TECH e para tasks correspondentes (Guardrail #8: \"Rastreabilidade obrigatoria \u00e2\u20ac\u201d toda User Story do PRD deve ter pelo menos uma task correspondente\").\n\n**Pergunta que faria ao usuario:**\n\n> O SPEC_TECH fornecido nao contem User Stories (US-XX) e nao referencia um PRD. No framework SDD, a rastreabilidade User Stories -> Tasks e obrigatoria no TASK PLAN.\n>\n> Temos duas opcoes:\n>\n> 1. **Voce fornece o PRD ou as User Stories** relacionadas a esta feature, e eu incluo a rastreabilidade completa no TASK PLAN.\n> 2. **Seguimos sem PRD/User Stories**, e a secao de rastreabilidade do TASK PLAN sera preenchida com \"N/A \u00e2\u20ac\u201d SPEC_TECH fornecido sem PRD/User Stories associadas\". Neste caso, a rastreabilidade sera feita apenas entre definicoes tecnicas do SPEC e tasks.\n>\n> Qual opcao prefere?\n\n---\n\n## 4. Ponto de Atencao \u00e2\u20ac\u201d Dependencia do Modulo de Produto\n\nAlem da questao de User Stories, identifiquei uma dependencia critica:\n\n**Pergunta que faria ao usuario (apos resposta da pergunta anterior):**\n\n> O SPEC_TECH menciona que o `ProductService` passara a consultar o cache antes do repository, e que operacoes de escrita (Create, Update, Delete) invalidarao entradas do cache. Porem, **nao existe nenhum modulo de Produto no codebase atual** \u00e2\u20ac\u201d nao ha proto, migration, queries, repository, service ou handler de produtos.\n>\n> Duas opcoes:\n>\n> 1. **O escopo deste TASK PLAN inclui a criacao do modulo de Produto completo** (migration, queries SQLC, repository, service, handler gRPC, proto) alem do cache. Isso aumenta significativamente o numero de tasks.\n> 2. **O modulo de Produto sera criado em outra feature/SPEC_TECH**, e este TASK PLAN assume que ele ja existira quando as tasks forem executadas. Neste caso, incluo apenas as tasks de cache + integracao com o ProductService.\n>\n> Qual abordagem seguir?\n\n---\n\n## 5. Confirmacao do Nome e Inicio\n\nApos as respostas acima, a proxima interacao seria:\n\n> Obrigado! Vamos iniciar o TASK PLAN para **Cache de Cardapio em Memoria (v1)**. Podemos iniciar a definicao macro das fases?\n\n---\n\n## Resumo da Interacao Inicial\n\n| Etapa | Status |\n|-------|--------|\n| Leitura do SPEC_TECH | Concluido |\n| Extracao do nome da feature | Concluido: `cache-de-cardapio` |\n| Analise do codebase | Concluido |\n| Identificacao de User Stories/PRD ausentes | Concluido \u00e2\u20ac\u201d pergunta formulada |\n| Identificacao de dependencia critica (modulo Produto) | Concluido \u00e2\u20ac\u201d pergunta formulada |\n| Confirmacao do nome e inicio das fases | Aguardando respostas anteriores |\n\n**Proximo passo**: Aguardar respostas do usuario para as duas perguntas acima antes de avancar para a definicao macro das fases.\n"}], "grading": {"eval_id": 3, "eval_name": "spec-tech-sem-user-stories-explicitas", "configuration": "with_skill", "expectations": [{"text": "Detecta a ausencia de User Stories (US-XX) e PRD referenciado no SPEC_TECH", "passed": true, "evidence": "Secao 3 explicita: 'O SPEC_TECH fornecido nao contem User Stories (US-XX) e nao referencia nenhum PRD.' Tambem identificou a dependencia critica do modulo de Produto inexistente."}, {"text": "PERGUNTA ao usuario sobre User Stories / PRD em vez de inventar ou ignorar", "passed": true, "evidence": "Formulou pergunta clara com 2 opcoes: (1) usuario fornece PRD/US ou (2) seguir sem, com rastreabilidade N/A. Aguarda resposta antes de avancar."}, {"text": "NAO inventa User Stories ficticias (guardrail: nunca invente informacoes)", "passed": true, "evidence": "Nenhuma User Story ficticia foi inventada. A resposta explicitamente identifica a ausencia e pergunta ao usuario."}, {"text": "Extrai corretamente o nome da feature como 'cache-de-cardapio' ou similar em kebab-case", "passed": true, "evidence": "Extraiu 'Cache de Cardapio em Memoria' e normalizou para 'cache-de-cardapio' em kebab-case."}, {"text": "Segue o processo interativo (uma pergunta por vez, confirma nome antes de prosseguir)", "passed": true, "evidence": "Seguiu sequencia: (1) extraiu nome, (2) perguntou sobre US/PRD ausentes, (3) perguntou sobre dependencia do modulo Produto. Indicou que confirmacao do nome e inicio das fases aguardam respostas anteriores."}]}}, {"id": "eval-3-sem-user-stories-without_skill", "prompt": "Gere o TASK PLAN para esta feature. Segue o SPEC_TECH aprovado:\n\n---\n# SPEC_TECH \u00e2\u20ac\u201d Cache de Cardapio v1\n\n## 1. Identificacao\n| Campo | Valor |\n|-------|-------|\n| **Feature/Projeto** | Cache de Cardapio em Memoria |\n| **Versao** | v1 |\n| **Data** | 2026-03-07 |\n\n## 2. Resumo Tecnico\nImplementar cache in-memory (sync.Map) para o cardapio de produtos, evitando queries repetidas ao SQLite. TTL configuravel via config.yaml.\n\n## 3. Definicoes Tecnicas\n\n### 3.1 Novo Pacote\n- `internal/infra/cache/` com interface `Cache[K, V]` e implementacao `InMemoryCache`\n- TTL configuravel via `cache.ttl_seconds` no config.yaml\n- Invalidacao manual via metodo `Invalidate(key)`\n\n### 3.2 Service\n- ProductService passa a consultar cache antes do repository\n- Cache miss: busca no repository e popula cache\n- Operacoes de escrita (Create, Update, Delete) invalidam a entrada correspondente\n\n### 3.3 Config\n- Novo campo `cache.ttl_seconds` (padrao: 300)\n\n### 3.4 DI\n- Registrar modulo cache no FX\n---", "eval_id": 3, "outputs": [{"name": "response.md", "type": "text", "content": "# TASK PLAN -- Cache de Cardapio v1\n\n## 1. Identificacao\n\n| Campo | Valor |\n|--------------------|--------------------------------|\n| **Feature/Projeto** | Cache de Cardapio em Memoria |\n| **Versao** | v1 |\n| **Data** | 2026-03-07 |\n| **SPEC_TECH** | Cache de Cardapio v1 |\n| **Branch base** | user-feature |\n\n---\n\n## 2. Observacoes Preliminares\n\nO SPEC_TECH nao referencia User Stories nem PRD. Isso implica que:\n- Nao ha criterios de aceitacao formais vindos de um PRD.\n- Os criterios de aceitacao serao derivados diretamente das definicoes tecnicas do SPEC_TECH.\n- Nao ha modulo de Product/Produto implementado no codebase atual. A API possui apenas o modulo User. Portanto, **antes de implementar o cache do cardapio, e necessario que o modulo Product (repository, service, handler, proto, migrations, queries SQLC) ja exista**. Este task plan assume que o modulo Product sera implementado como pre-requisito ou em paralelo.\n\n---\n\n## 3. Pre-requisitos\n\nAntes de iniciar as tasks deste plano, os seguintes itens devem estar prontos:\n\n| # | Pre-requisito | Status |\n|---|---------------|--------|\n| P1 | Modulo Product completo (proto, migration, queries SQLC, repository, service, handler) | Pendente -- nao existe no codebase |\n| P2 | Queries SQLC para produtos geradas (`make sqlc`) | Pendente |\n| P3 | Proto de produto gerado (`make proto`) | Pendente |\n\n> **Nota:** Se o modulo Product nao existir, as Tasks 4 e 5 nao podem ser implementadas. As Tasks 1-3 (infraestrutura de cache) sao independentes e podem ser feitas antes.\n\n---\n\n## 4. Tasks\n\n### Task 1 -- Adicionar configuracao de cache ao config.yaml e struct Config\n\n**Objetivo:** Expor o campo `cache.ttl_seconds` na configuracao da aplicacao.\n\n**Arquivos afetados:**\n- `configs/config.yaml`\n- `internal/infra/config/config.go`\n- `internal/infra/config/config_test.go`\n\n**Subtasks:**\n\n| # | Descricao | Detalhe |\n|---|-----------|---------|\n| 1.1 | Adicionar secao `cache` ao `configs/config.yaml` | Adicionar `cache.ttl_seconds: 300` (valor padrao) |\n| 1.2 | Adicionar campo `CacheTTLSeconds int` na struct `Config` | Em `internal/infra/config/config.go` |\n| 1.3 | Popular o campo via Viper | `v.GetInt(\"cache.ttl_seconds\")` com fallback para 300 se nao configurado |\n| 1.4 | Adicionar teste unitario | Verificar que o valor padrao e 300 e que override via env (`APP_CACHE_TTL_SECONDS`) funciona |\n\n**Criterios de aceite:**\n- `Config.CacheTTLSeconds` retorna 300 quando nao configurado.\n- `Config.CacheTTLSeconds` respeita valor do YAML e override via variavel de ambiente.\n- Testes passam.\n\n**Estimativa:** P (pequena)\n\n---\n\n### Task 2 -- Criar pacote de cache generico\n\n**Objetivo:** Implementar `internal/infra/cache/` com interface generica `Cache[K, V]` e implementacao `InMemoryCache` usando `sync.Map`.\n\n**Arquivos a criar:**\n- `internal/infra/cache/cache.go` -- interface e implementacao\n- `internal/infra/cache/cache_test.go` -- testes unitarios\n- `internal/infra/cache/fx.go` -- modulo FX\n\n**Subtasks:**\n\n| # | Descricao | Detalhe |\n|---|-----------|---------|\n| 2.1 | Definir interface `Cache[K comparable, V any]` | Metodos: `Get(key K) (V, bool)`, `Set(key K, value V)`, `Invalidate(key K)`, `InvalidateAll()` |\n| 2.2 | Implementar `InMemoryCache[K, V]` | Usar `sync.Map` internamente. Cada entrada armazena o valor e o timestamp de insercao. No `Get`, verificar se `time.Since(timestamp) > ttl`; se sim, deletar e retornar `false`. |\n| 2.3 | Construtor `NewInMemoryCache[K, V](ttl time.Duration) *InMemoryCache[K, V]` | Recebe TTL como parametro |\n| 2.4 | Escrever testes unitarios | Testar: Get/Set basico, expiricao por TTL, Invalidate de chave unica, InvalidateAll |\n| 2.5 | Criar modulo FX (`fx.go`) | Nao registrar provider generico diretamente -- o modulo FX servira como ponto de importacao. A instanciacao concreta sera feita no service ou via factory (ver Task 4). |\n\n**Decisoes tecnicas:**\n- `sync.Map` e thread-safe e adequado para cenarios read-heavy (cache de cardapio).\n- Cada entry no map sera um struct `cacheEntry[V]{ value V, createdAt time.Time }`.\n- A verificacao de TTL e lazy (no momento do `Get`), sem goroutine de limpeza periodica em v1.\n\n**Criterios de aceite:**\n- Interface `Cache[K, V]` definida e exportada.\n- `InMemoryCache` implementa a interface corretamente.\n- TTL e respeitado: itens expirados nao sao retornados por `Get`.\n- `Invalidate` remove entrada especifica; `InvalidateAll` limpa todo o cache.\n- Testes passam com cobertura dos cenarios listados.\n\n**Estimativa:** M (media)\n\n---\n\n### Task 3 -- Registrar modulo cache no FX (DI)\n\n**Objetivo:** Integrar o modulo de cache na composicao de dependencias da aplicacao.\n\n**Arquivos afetados:**\n- `internal/infra/di/fx.go`\n- `internal/infra/cache/fx.go` (criado na Task 2)\n\n**Subtasks:**\n\n| # | Descricao | Detalhe |\n|---|-----------|---------|\n| 3.1 | Criar factory function para cache de produtos | `NewProductCache(cfg *config.Config) *InMemoryCache[string, *repository.Product]` -- usa `cfg.CacheTTLSeconds` para definir o TTL |\n| 3.2 | Registrar no modulo FX do cache | `fx.Provide(NewProductCache)` |\n| 3.3 | Adicionar `cache.Module` ao `AppModule()` em `internal/infra/di/fx.go` | Posicionar entre `config.Module` e `repository.Module` na lista de modulos |\n| 3.4 | Atualizar teste de DI | Verificar que `fx.ValidateApp` passa com o novo modulo |\n\n**Dependencias:** Task 1 (config), Task 2 (pacote cache), Pre-requisito P1 (modulo Product -- para o tipo `repository.Product`)\n\n**Criterios de aceite:**\n- `cache.Module` esta registrado em `AppModule()`.\n- Aplicacao inicializa sem erros com o novo modulo.\n- Teste de validacao FX passa.\n\n**Estimativa:** P (pequena)\n\n---\n\n### Task 4 -- Integrar cache no ProductService\n\n**Objetivo:** Modificar o `ProductService` para consultar cache antes do repository, e invalidar cache em operacoes de escrita.\n\n**Arquivos afetados:**\n- `internal/service/product_service.go` (a ser criado no pre-requisito ou modificado se ja existir)\n- `internal/service/product_service_test.go`\n\n**Subtasks:**\n\n| # | Descricao | Detalhe |\n|---|-----------|---------|\n| 4.1 | Adicionar dependencia de cache ao `productService` | Novo campo `cache cache.Cache[string, *repository.Product]` na struct |\n| 4.2 | Atualizar construtor `NewProductService` | Receber `*InMemoryCache[string, *repository.Product]` como parametro |\n| 4.3 | Implementar cache-aside no `GetProduct` | 1) Consultar `cache.Get(id)`. 2) Se hit, retornar direto. 3) Se miss, buscar no repository, chamar `cache.Set(id, product)`, retornar. |\n| 4.4 | Invalidar cache em `CreateProduct` | Apos insert no repository, chamar `cache.Invalidate(id)` (ou nao popular para evitar inconsistencia) |\n| 4.5 | Invalidar cache em `UpdateProduct` | Apos update no repository, chamar `cache.Invalidate(id)` |\n| 4.6 | Invalidar cache em `DeleteProduct` | Apos delete no repository, chamar `cache.Invalidate(id)` |\n| 4.7 | Adicionar logs de cache hit/miss | `s.logger.Debug(\"cache hit para produto\", zap.String(\"id\", id))` e `s.logger.Debug(\"cache miss para produto\", zap.String(\"id\", id))` |\n| 4.8 | Escrever testes unitarios | Testar: cache hit retorna sem chamar repo, cache miss chama repo e popula cache, create/update/delete invalidam cache |\n\n**Dependencias:** Task 2 (pacote cache), Task 3 (DI), Pre-requisito P1 (modulo Product)\n\n**Criterios de aceite:**\n- `GetProduct` retorna do cache quando disponivel (sem query ao banco).\n- `GetProduct` busca do repository em cache miss e popula o cache.\n- `CreateProduct`, `UpdateProduct`, `DeleteProduct` invalidam a entrada correspondente no cache.\n- Logs de debug indicam hit/miss.\n- Testes unitarios com mock de cache e mock de repository passam.\n- Nenhuma regressao nos testes existentes.\n\n**Estimativa:** M (media)\n\n---\n\n### Task 5 -- Testes de integracao e validacao final\n\n**Objetivo:** Garantir que o fluxo completo funciona end-to-end e que o cache se comporta corretamente sob uso real.\n\n**Arquivos afetados:**\n- `internal/service/product_service_test.go` (testes adicionais)\n\n**Subtasks:**\n\n| # | Descricao | Detalhe |\n|---|-----------|---------|\n| 5.1 | Teste de cenario completo | Criar produto -> Get (cache miss, popula) -> Get novamente (cache hit) -> Update (invalida) -> Get (cache miss novamente) |\n| 5.2 | Teste de expiracao por TTL | Criar produto -> Get (popula cache com TTL curto) -> esperar expiracao -> Get (cache miss) |\n| 5.3 | Teste de concorrencia | Multiplas goroutines fazendo Get simultaneo no mesmo produto -- garantir que nao ha race condition |\n| 5.4 | Rodar `make test` completo | Garantir que nenhum teste existente quebrou |\n| 5.5 | Rodar `make build` | Garantir que a compilacao passa |\n\n**Dependencias:** Tasks 1-4 completas\n\n**Criterios de aceite:**\n- Todos os cenarios de teste passam.\n- `go test -race ./internal/...` nao detecta race conditions.\n- `make build` e `make test` passam sem erros.\n\n**Estimativa:** M (media)\n\n---\n\n## 5. Ordem de Execucao e Dependencias\n\n```\nTask 1 (Config) \u00e2\u201d\u20ac\u00e2\u201d\u20ac\u00e2\u201d\u20ac\u00e2\u201d\u20ac\u00e2\u201d\u20ac\u00e2\u201d\ufffd\n \u00e2\u201d\u0153\u00e2\u201d\u20ac\u00e2\u201d\u20ac> Task 3 (DI) \u00e2\u201d\u20ac\u00e2\u201d\u20ac> Task 4 (Service) \u00e2\u201d\u20ac\u00e2\u201d\u20ac> Task 5 (Validacao)\nTask 2 (Cache pkg) \u00e2\u201d\u20ac\u00e2\u201d\u20ac\u00e2\u201d\u02dc\n```\n\n- Tasks 1 e 2 podem ser executadas **em paralelo**.\n- Task 3 depende de Tasks 1 e 2.\n- Task 4 depende de Task 3 e do pre-requisito P1 (modulo Product).\n- Task 5 depende de Task 4.\n\n---\n\n## 6. Resumo de Estimativas\n\n| Task | Descricao | Estimativa | Dependencias |\n|------|-----------|------------|--------------|\n| 1 | Config de cache | P (pequena) | Nenhuma |\n| 2 | Pacote cache generico | M (media) | Nenhuma |\n| 3 | Registro no FX (DI) | P (pequena) | Tasks 1, 2, P1 |\n| 4 | Integracao no ProductService | M (media) | Task 3, P1 |\n| 5 | Testes e validacao | M (media) | Task 4 |\n\n**Esforco total estimado:** ~1.5 a 2 dias de desenvolvimento.\n\n---\n\n## 7. Riscos e Consideracoes\n\n| # | Risco | Mitigacao |\n|---|-------|-----------|\n| R1 | Modulo Product nao existe no codebase -- bloqueia Tasks 3-5 | Implementar modulo Product antes ou em paralelo. Tasks 1-2 sao independentes. |\n| R2 | `sync.Map` nao suporta generics nativamente (usa `any` internamente) | Wrapper tipado com type assertions no `InMemoryCache`. Testes garantem type safety. |\n| R3 | Lazy expiration pode acumular entradas expiradas em memoria | Aceitavel em v1 para um cardapio pequeno. Em v2, considerar goroutine de limpeza periodica. |\n| R4 | Cache pode servir dados stale em cenarios de escrita concorrente | Invalidacao sincrona no mesmo request de escrita mitiga isso. TTL garante convergencia eventual. |\n| R5 | Sem metricas de cache (hit rate, tamanho) | Fora de escopo em v1. Considerar para v2. |\n\n---\n\n## 8. Arquivos Criados/Modificados (Resumo)\n\n### Novos arquivos:\n- `internal/infra/cache/cache.go`\n- `internal/infra/cache/cache_test.go`\n- `internal/infra/cache/fx.go`\n\n### Arquivos modificados:\n- `configs/config.yaml` -- adicionar secao `cache`\n- `internal/infra/config/config.go` -- adicionar campo `CacheTTLSeconds`\n- `internal/infra/config/config_test.go` -- testes do novo campo\n- `internal/infra/di/fx.go` -- registrar `cache.Module`\n- `internal/infra/di/fx_test.go` -- atualizar teste de validacao\n- `internal/service/product_service.go` -- integrar cache (requer que o arquivo exista)\n- `internal/service/product_service_test.go` -- testes de cache no service\n"}], "grading": {"eval_id": 3, "eval_name": "spec-tech-sem-user-stories-explicitas", "configuration": "without_skill", "expectations": [{"text": "Detecta a ausencia de User Stories (US-XX) e PRD referenciado no SPEC_TECH", "passed": true, "evidence": "Secao 2 'Observacoes Preliminares' nota: 'O SPEC_TECH nao referencia User Stories nem PRD.' Tambem detectou que o modulo Product nao existe."}, {"text": "PERGUNTA ao usuario sobre User Stories / PRD em vez de inventar ou ignorar", "passed": false, "evidence": "Nao perguntou ao usuario. Apenas notou a ausencia como observacao e prosseguiu gerando o plano completo com 5 tasks sem interacao."}, {"text": "NAO inventa User Stories ficticias (guardrail: nunca invente informacoes)", "passed": true, "evidence": "Nao inventou User Stories. Derivou criterios diretamente do SPEC_TECH."}, {"text": "Extrai corretamente o nome da feature como 'cache-de-cardapio' ou similar em kebab-case", "passed": false, "evidence": "Usou 'Cache de Cardapio em Memoria' como titulo mas nao normalizou para kebab-case. Nenhuma convencao de nomenclatura aplicada."}, {"text": "Segue o processo interativo (uma pergunta por vez, confirma nome antes de prosseguir)", "passed": false, "evidence": "Nenhum processo interativo. Gerou o plano completo em um unico turno sem perguntas ao usuario."}]}}], "previous_feedback": {}, "previous_outputs": {}, "benchmark": {"skill_name": "sdd-task-plan-expert", "iteration": 1, "configurations": [{"name": "with_skill", "pass_rate": 0.933, "mean_tokens": 55027, "stddev_tokens": 10217, "mean_duration_seconds": 184.9, "stddev_duration_seconds": 85.7, "evals": [{"eval_name": "happy-path-spec-tech-usuario", "pass_rate": 0.8, "passed": 4, "total": 5, "tokens": 61221, "duration_seconds": 138.2, "failed_assertions": ["Le as rules do projeto (.claude/rules/ e CLAUDE.md) ANTES de gerar tasks"]}, {"eval_name": "spec-tech-simples-alteracao-pontual", "pass_rate": 1.0, "passed": 5, "total": 5, "tokens": 60596, "duration_seconds": 286.6, "failed_assertions": []}, {"eval_name": "spec-tech-sem-user-stories-explicitas", "pass_rate": 1.0, "passed": 5, "total": 5, "tokens": 43263, "duration_seconds": 130.0, "failed_assertions": []}]}, {"name": "without_skill", "pass_rate": 0.333, "mean_tokens": 38834, "stddev_tokens": 3912, "mean_duration_seconds": 151.2, "stddev_duration_seconds": 14.5, "evals": [{"eval_name": "happy-path-spec-tech-usuario", "pass_rate": 0.2, "passed": 1, "total": 5, "tokens": 40363, "duration_seconds": 141.3, "failed_assertions": ["Extrai o nome da feature do SPEC_TECH e confirma com o usuario antes de prosseguir", "Faz apenas UMA pergunta por vez", "Propoe macro-fases de alto nivel e aguarda validacao antes de criar tasks", "Le as rules do projeto (.claude/rules/ e CLAUDE.md) ANTES de gerar tasks"]}, {"eval_name": "spec-tech-simples-alteracao-pontual", "pass_rate": 0.2, "passed": 1, "total": 5, "tokens": 41818, "duration_seconds": 167.6, "failed_assertions": ["Gera um numero proporcional de tasks (entre 3 e 6)", "Mapeia a US-10 na tabela de rastreabilidade do task_plan", "Cada task segue o template completo (secoes 1-8)", "Salva task_plan.md como documento de REFERENCIA"]}, {"eval_name": "spec-tech-sem-user-stories-explicitas", "pass_rate": 0.4, "passed": 2, "total": 5, "tokens": 34322, "duration_seconds": 144.7, "failed_assertions": ["PERGUNTA ao usuario sobre User Stories / PRD em vez de inventar ou ignorar", "Extrai corretamente o nome da feature em kebab-case", "Segue o processo interativo (uma pergunta por vez)"]}]}], "delta": {"pass_rate_improvement": "+60.0pp", "tokens_overhead": "+41.7%", "duration_overhead": "+22.3%"}}};
650
+
651
+ // ---- State ----
652
+ let feedbackMap = {}; // run_id -> feedback text
653
+ let currentIndex = 0;
654
+ let visitedRuns = new Set();
655
+
656
+ // ---- Init ----
657
+ async function init() {
658
+ // Load saved feedback from server — but only if this isn't a fresh
659
+ // iteration (indicated by previous_feedback being present). When
660
+ // previous feedback exists, the feedback.json on disk is stale from
661
+ // the prior iteration and should not pre-fill the textareas.
662
+ const hasPrevious = Object.keys(EMBEDDED_DATA.previous_feedback || {}).length > 0
663
+ || Object.keys(EMBEDDED_DATA.previous_outputs || {}).length > 0;
664
+ if (!hasPrevious) {
665
+ try {
666
+ const resp = await fetch("/api/feedback");
667
+ const data = await resp.json();
668
+ if (data.reviews) {
669
+ for (const r of data.reviews) feedbackMap[r.run_id] = r.feedback;
670
+ }
671
+ } catch { /* first run, no feedback yet */ }
672
+ }
673
+
674
+ document.getElementById("skill-name").textContent = EMBEDDED_DATA.skill_name;
675
+ showRun(0);
676
+
677
+ // Wire up feedback auto-save
678
+ const textarea = document.getElementById("feedback");
679
+ let saveTimeout = null;
680
+ textarea.addEventListener("input", () => {
681
+ clearTimeout(saveTimeout);
682
+ document.getElementById("feedback-status").textContent = "";
683
+ saveTimeout = setTimeout(() => saveCurrentFeedback(), 800);
684
+ });
685
+ }
686
+
687
+ // ---- Navigation ----
688
+ function navigate(delta) {
689
+ const newIndex = currentIndex + delta;
690
+ if (newIndex >= 0 && newIndex < EMBEDDED_DATA.runs.length) {
691
+ saveCurrentFeedback();
692
+ showRun(newIndex);
693
+ }
694
+ }
695
+
696
+ function updateNavButtons() {
697
+ document.getElementById("prev-btn").disabled = currentIndex === 0;
698
+ document.getElementById("next-btn").disabled =
699
+ currentIndex === EMBEDDED_DATA.runs.length - 1;
700
+ }
701
+
702
+ // ---- Show a run ----
703
+ function showRun(index) {
704
+ currentIndex = index;
705
+ const run = EMBEDDED_DATA.runs[index];
706
+
707
+ // Progress
708
+ document.getElementById("progress").textContent =
709
+ `${index + 1} of ${EMBEDDED_DATA.runs.length}`;
710
+
711
+ // Prompt
712
+ document.getElementById("prompt-text").textContent = run.prompt;
713
+
714
+ // Config badge
715
+ const badge = document.getElementById("config-badge");
716
+ const configMatch = run.id.match(/(with_skill|without_skill|new_skill|old_skill)/);
717
+ if (configMatch) {
718
+ const config = configMatch[1];
719
+ const isBaseline = config === "without_skill" || config === "old_skill";
720
+ badge.textContent = config.replace(/_/g, " ");
721
+ badge.className = "config-badge " + (isBaseline ? "config-baseline" : "config-primary");
722
+ badge.style.display = "inline-block";
723
+ } else {
724
+ badge.style.display = "none";
725
+ }
726
+
727
+ // Outputs
728
+ renderOutputs(run);
729
+
730
+ // Previous outputs
731
+ renderPrevOutputs(run);
732
+
733
+ // Grades
734
+ renderGrades(run);
735
+
736
+ // Previous feedback
737
+ const prevFb = (EMBEDDED_DATA.previous_feedback || {})[run.id];
738
+ const prevEl = document.getElementById("prev-feedback");
739
+ if (prevFb) {
740
+ document.getElementById("prev-feedback-text").textContent = prevFb;
741
+ prevEl.style.display = "block";
742
+ } else {
743
+ prevEl.style.display = "none";
744
+ }
745
+
746
+ // Feedback
747
+ document.getElementById("feedback").value = feedbackMap[run.id] || "";
748
+ document.getElementById("feedback-status").textContent = "";
749
+
750
+ updateNavButtons();
751
+
752
+ // Track visited runs and promote done button when all visited
753
+ visitedRuns.add(index);
754
+ const doneBtn = document.getElementById("done-btn");
755
+ if (visitedRuns.size >= EMBEDDED_DATA.runs.length) {
756
+ doneBtn.classList.add("ready");
757
+ }
758
+
759
+ // Scroll main content to top
760
+ document.querySelector(".main").scrollTop = 0;
761
+ }
762
+
763
+ // ---- Render outputs ----
764
+ function renderOutputs(run) {
765
+ const container = document.getElementById("outputs-body");
766
+ container.innerHTML = "";
767
+
768
+ const outputs = run.outputs || [];
769
+ if (outputs.length === 0) {
770
+ container.innerHTML = '<div class="empty-state">No output files</div>';
771
+ return;
772
+ }
773
+
774
+ for (const file of outputs) {
775
+ const fileDiv = document.createElement("div");
776
+ fileDiv.className = "output-file";
777
+
778
+ // Always show file header with download link
779
+ const header = document.createElement("div");
780
+ header.className = "output-file-header";
781
+ const nameSpan = document.createElement("span");
782
+ nameSpan.textContent = file.name;
783
+ header.appendChild(nameSpan);
784
+ const dlBtn = document.createElement("a");
785
+ dlBtn.className = "dl-btn";
786
+ dlBtn.textContent = "Download";
787
+ dlBtn.download = file.name;
788
+ dlBtn.href = getDownloadUri(file);
789
+ header.appendChild(dlBtn);
790
+ fileDiv.appendChild(header);
791
+
792
+ const content = document.createElement("div");
793
+ content.className = "output-file-content";
794
+
795
+ if (file.type === "text") {
796
+ const pre = document.createElement("pre");
797
+ pre.textContent = file.content;
798
+ content.appendChild(pre);
799
+ } else if (file.type === "image") {
800
+ const img = document.createElement("img");
801
+ img.src = file.data_uri;
802
+ img.alt = file.name;
803
+ content.appendChild(img);
804
+ } else if (file.type === "pdf") {
805
+ const iframe = document.createElement("iframe");
806
+ iframe.src = file.data_uri;
807
+ content.appendChild(iframe);
808
+ } else if (file.type === "xlsx") {
809
+ renderXlsx(content, file.data_b64);
810
+ } else if (file.type === "binary") {
811
+ const a = document.createElement("a");
812
+ a.className = "download-link";
813
+ a.href = file.data_uri;
814
+ a.download = file.name;
815
+ a.textContent = "Download " + file.name;
816
+ content.appendChild(a);
817
+ } else if (file.type === "error") {
818
+ const pre = document.createElement("pre");
819
+ pre.textContent = file.content;
820
+ pre.style.color = "var(--red)";
821
+ content.appendChild(pre);
822
+ }
823
+
824
+ fileDiv.appendChild(content);
825
+ container.appendChild(fileDiv);
826
+ }
827
+ }
828
+
829
+ // ---- XLSX rendering via SheetJS ----
830
+ function renderXlsx(container, b64Data) {
831
+ try {
832
+ const raw = Uint8Array.from(atob(b64Data), c => c.charCodeAt(0));
833
+ const wb = XLSX.read(raw, { type: "array" });
834
+
835
+ for (let i = 0; i < wb.SheetNames.length; i++) {
836
+ const sheetName = wb.SheetNames[i];
837
+ const ws = wb.Sheets[sheetName];
838
+
839
+ if (wb.SheetNames.length > 1) {
840
+ const sheetLabel = document.createElement("div");
841
+ sheetLabel.style.cssText =
842
+ "font-weight:600; font-size:0.8rem; color:#b0aea5; margin-top:0.5rem; margin-bottom:0.25rem;";
843
+ sheetLabel.textContent = "Sheet: " + sheetName;
844
+ container.appendChild(sheetLabel);
845
+ }
846
+
847
+ const htmlStr = XLSX.utils.sheet_to_html(ws, { editable: false });
848
+ const wrapper = document.createElement("div");
849
+ wrapper.innerHTML = htmlStr;
850
+ container.appendChild(wrapper);
851
+ }
852
+ } catch (err) {
853
+ container.textContent = "Error rendering spreadsheet: " + err.message;
854
+ }
855
+ }
856
+
857
+ // ---- Grades ----
858
+ function renderGrades(run) {
859
+ const section = document.getElementById("grades-section");
860
+ const content = document.getElementById("grades-content");
861
+
862
+ if (!run.grading) {
863
+ section.style.display = "none";
864
+ return;
865
+ }
866
+
867
+ const grading = run.grading;
868
+ section.style.display = "block";
869
+ // Reset to collapsed
870
+ content.classList.remove("open");
871
+ document.getElementById("grades-arrow").classList.remove("open");
872
+
873
+ const summary = grading.summary || {};
874
+ const expectations = grading.expectations || [];
875
+
876
+ let html = '<div style="padding: 1rem;">';
877
+
878
+ // Summary line
879
+ const passRate = summary.pass_rate != null
880
+ ? Math.round(summary.pass_rate * 100) + "%"
881
+ : "?";
882
+ const badgeClass = summary.pass_rate >= 0.8 ? "grade-pass" : summary.pass_rate >= 0.5 ? "" : "grade-fail";
883
+ html += '<div class="grades-summary">';
884
+ html += '<span class="grade-badge ' + badgeClass + '">' + passRate + '</span>';
885
+ html += '<span>' + (summary.passed || 0) + ' passed, ' + (summary.failed || 0) + ' failed of ' + (summary.total || 0) + '</span>';
886
+ html += '</div>';
887
+
888
+ // Assertions list
889
+ html += '<ul class="assertion-list">';
890
+ for (const exp of expectations) {
891
+ const statusClass = exp.passed ? "pass" : "fail";
892
+ const statusIcon = exp.passed ? "\u2713" : "\u2717";
893
+ html += '<li class="assertion-item">';
894
+ html += '<span class="assertion-status ' + statusClass + '">' + statusIcon + '</span>';
895
+ html += '<span>' + escapeHtml(exp.text) + '</span>';
896
+ if (exp.evidence) {
897
+ html += '<div class="assertion-evidence">' + escapeHtml(exp.evidence) + '</div>';
898
+ }
899
+ html += '</li>';
900
+ }
901
+ html += '</ul>';
902
+
903
+ html += '</div>';
904
+ content.innerHTML = html;
905
+ }
906
+
907
+ function toggleGrades() {
908
+ const content = document.getElementById("grades-content");
909
+ const arrow = document.getElementById("grades-arrow");
910
+ content.classList.toggle("open");
911
+ arrow.classList.toggle("open");
912
+ }
913
+
914
+ // ---- Previous outputs (collapsible) ----
915
+ function renderPrevOutputs(run) {
916
+ const section = document.getElementById("prev-outputs-section");
917
+ const content = document.getElementById("prev-outputs-content");
918
+ const prevOutputs = (EMBEDDED_DATA.previous_outputs || {})[run.id];
919
+
920
+ if (!prevOutputs || prevOutputs.length === 0) {
921
+ section.style.display = "none";
922
+ return;
923
+ }
924
+
925
+ section.style.display = "block";
926
+ // Reset to collapsed
927
+ content.classList.remove("open");
928
+ document.getElementById("prev-outputs-arrow").classList.remove("open");
929
+
930
+ // Render the files into the content area
931
+ content.innerHTML = "";
932
+ const wrapper = document.createElement("div");
933
+ wrapper.style.padding = "1rem";
934
+
935
+ for (const file of prevOutputs) {
936
+ const fileDiv = document.createElement("div");
937
+ fileDiv.className = "output-file";
938
+
939
+ const header = document.createElement("div");
940
+ header.className = "output-file-header";
941
+ const nameSpan = document.createElement("span");
942
+ nameSpan.textContent = file.name;
943
+ header.appendChild(nameSpan);
944
+ const dlBtn = document.createElement("a");
945
+ dlBtn.className = "dl-btn";
946
+ dlBtn.textContent = "Download";
947
+ dlBtn.download = file.name;
948
+ dlBtn.href = getDownloadUri(file);
949
+ header.appendChild(dlBtn);
950
+ fileDiv.appendChild(header);
951
+
952
+ const fc = document.createElement("div");
953
+ fc.className = "output-file-content";
954
+
955
+ if (file.type === "text") {
956
+ const pre = document.createElement("pre");
957
+ pre.textContent = file.content;
958
+ fc.appendChild(pre);
959
+ } else if (file.type === "image") {
960
+ const img = document.createElement("img");
961
+ img.src = file.data_uri;
962
+ img.alt = file.name;
963
+ fc.appendChild(img);
964
+ } else if (file.type === "pdf") {
965
+ const iframe = document.createElement("iframe");
966
+ iframe.src = file.data_uri;
967
+ fc.appendChild(iframe);
968
+ } else if (file.type === "xlsx") {
969
+ renderXlsx(fc, file.data_b64);
970
+ } else if (file.type === "binary") {
971
+ const a = document.createElement("a");
972
+ a.className = "download-link";
973
+ a.href = file.data_uri;
974
+ a.download = file.name;
975
+ a.textContent = "Download " + file.name;
976
+ fc.appendChild(a);
977
+ }
978
+
979
+ fileDiv.appendChild(fc);
980
+ wrapper.appendChild(fileDiv);
981
+ }
982
+
983
+ content.appendChild(wrapper);
984
+ }
985
+
986
+ function togglePrevOutputs() {
987
+ const content = document.getElementById("prev-outputs-content");
988
+ const arrow = document.getElementById("prev-outputs-arrow");
989
+ content.classList.toggle("open");
990
+ arrow.classList.toggle("open");
991
+ }
992
+
993
+ // ---- Feedback (saved to server -> feedback.json) ----
994
+ function saveCurrentFeedback() {
995
+ const run = EMBEDDED_DATA.runs[currentIndex];
996
+ const text = document.getElementById("feedback").value;
997
+
998
+ if (text.trim() === "") {
999
+ delete feedbackMap[run.id];
1000
+ } else {
1001
+ feedbackMap[run.id] = text;
1002
+ }
1003
+
1004
+ // Build reviews array from map
1005
+ const reviews = [];
1006
+ for (const [run_id, feedback] of Object.entries(feedbackMap)) {
1007
+ if (feedback.trim()) {
1008
+ reviews.push({ run_id, feedback, timestamp: new Date().toISOString() });
1009
+ }
1010
+ }
1011
+
1012
+ fetch("/api/feedback", {
1013
+ method: "POST",
1014
+ headers: { "Content-Type": "application/json" },
1015
+ body: JSON.stringify({ reviews, status: "in_progress" }),
1016
+ }).then(() => {
1017
+ document.getElementById("feedback-status").textContent = "Saved";
1018
+ }).catch(() => {
1019
+ // Static mode or server unavailable — no-op on auto-save,
1020
+ // feedback will be downloaded on final submit
1021
+ document.getElementById("feedback-status").textContent = "Will download on submit";
1022
+ });
1023
+ }
1024
+
1025
+ // ---- Done ----
1026
+ function showDoneDialog() {
1027
+ // Save current textarea to feedbackMap (but don't POST yet)
1028
+ const run = EMBEDDED_DATA.runs[currentIndex];
1029
+ const text = document.getElementById("feedback").value;
1030
+ if (text.trim() === "") {
1031
+ delete feedbackMap[run.id];
1032
+ } else {
1033
+ feedbackMap[run.id] = text;
1034
+ }
1035
+
1036
+ // POST once with status: complete — include ALL runs so the model
1037
+ // can distinguish "no feedback" (looks good) from "not reviewed"
1038
+ const reviews = [];
1039
+ const ts = new Date().toISOString();
1040
+ for (const r of EMBEDDED_DATA.runs) {
1041
+ reviews.push({ run_id: r.id, feedback: feedbackMap[r.id] || "", timestamp: ts });
1042
+ }
1043
+ const payload = JSON.stringify({ reviews, status: "complete" }, null, 2);
1044
+ fetch("/api/feedback", {
1045
+ method: "POST",
1046
+ headers: { "Content-Type": "application/json" },
1047
+ body: payload,
1048
+ }).then(() => {
1049
+ document.getElementById("done-overlay").classList.add("visible");
1050
+ }).catch(() => {
1051
+ // Server not available (static mode) — download as file
1052
+ const blob = new Blob([payload], { type: "application/json" });
1053
+ const url = URL.createObjectURL(blob);
1054
+ const a = document.createElement("a");
1055
+ a.href = url;
1056
+ a.download = "feedback.json";
1057
+ a.click();
1058
+ URL.revokeObjectURL(url);
1059
+ document.getElementById("done-overlay").classList.add("visible");
1060
+ });
1061
+ }
1062
+
1063
+ function closeDoneDialog() {
1064
+ // Reset status back to in_progress
1065
+ saveCurrentFeedback();
1066
+ document.getElementById("done-overlay").classList.remove("visible");
1067
+ }
1068
+
1069
+ // ---- Toast ----
1070
+ function showToast(message) {
1071
+ const toast = document.getElementById("toast");
1072
+ toast.textContent = message;
1073
+ toast.classList.add("visible");
1074
+ setTimeout(() => toast.classList.remove("visible"), 2000);
1075
+ }
1076
+
1077
+ // ---- Keyboard nav ----
1078
+ document.addEventListener("keydown", (e) => {
1079
+ // Don't capture when typing in textarea
1080
+ if (e.target.tagName === "TEXTAREA") return;
1081
+
1082
+ if (e.key === "ArrowLeft" || e.key === "ArrowUp") {
1083
+ e.preventDefault();
1084
+ navigate(-1);
1085
+ } else if (e.key === "ArrowRight" || e.key === "ArrowDown") {
1086
+ e.preventDefault();
1087
+ navigate(1);
1088
+ }
1089
+ });
1090
+
1091
+ // ---- Util ----
1092
+ function getDownloadUri(file) {
1093
+ if (file.data_uri) return file.data_uri;
1094
+ if (file.data_b64) return "data:application/octet-stream;base64," + file.data_b64;
1095
+ if (file.type === "text") return "data:text/plain;charset=utf-8," + encodeURIComponent(file.content);
1096
+ return "#";
1097
+ }
1098
+
1099
+ function escapeHtml(text) {
1100
+ const div = document.createElement("div");
1101
+ div.textContent = text;
1102
+ return div.innerHTML;
1103
+ }
1104
+
1105
+ // ---- View switching ----
1106
+ function switchView(view) {
1107
+ document.querySelectorAll(".view-tab").forEach(t => t.classList.remove("active"));
1108
+ document.querySelectorAll(".view-panel").forEach(p => p.classList.remove("active"));
1109
+ document.querySelector(`[onclick="switchView('${view}')"]`).classList.add("active");
1110
+ document.getElementById("panel-" + view).classList.add("active");
1111
+ }
1112
+
1113
+ // ---- Benchmark rendering ----
1114
+ function renderBenchmark() {
1115
+ const data = EMBEDDED_DATA.benchmark;
1116
+ if (!data) return;
1117
+
1118
+ // Show the tabs
1119
+ document.getElementById("view-tabs").style.display = "flex";
1120
+
1121
+ const container = document.getElementById("benchmark-content");
1122
+ const summary = data.run_summary || {};
1123
+ const metadata = data.metadata || {};
1124
+ const notes = data.notes || [];
1125
+
1126
+ let html = "";
1127
+
1128
+ // Header
1129
+ html += "<h2 style='font-family: Poppins, sans-serif; margin-bottom: 0.5rem;'>Benchmark Results</h2>";
1130
+ html += "<p style='color: var(--text-muted); font-size: 0.875rem; margin-bottom: 1.25rem;'>";
1131
+ if (metadata.skill_name) html += "<strong>" + escapeHtml(metadata.skill_name) + "</strong> &mdash; ";
1132
+ if (metadata.timestamp) html += metadata.timestamp + " &mdash; ";
1133
+ if (metadata.evals_run) html += "Evals: " + metadata.evals_run.join(", ") + " &mdash; ";
1134
+ html += (metadata.runs_per_configuration || "?") + " runs per configuration";
1135
+ html += "</p>";
1136
+
1137
+ // Summary table
1138
+ html += '<table class="benchmark-table">';
1139
+
1140
+ function fmtStat(stat, pct) {
1141
+ if (!stat) return "—";
1142
+ const suffix = pct ? "%" : "";
1143
+ const m = pct ? (stat.mean * 100).toFixed(0) : stat.mean.toFixed(1);
1144
+ const s = pct ? (stat.stddev * 100).toFixed(0) : stat.stddev.toFixed(1);
1145
+ return m + suffix + " ± " + s + suffix;
1146
+ }
1147
+
1148
+ function deltaClass(val) {
1149
+ if (!val) return "";
1150
+ const n = parseFloat(val);
1151
+ if (n > 0) return "benchmark-delta-positive";
1152
+ if (n < 0) return "benchmark-delta-negative";
1153
+ return "";
1154
+ }
1155
+
1156
+ // Discover config names dynamically (everything except "delta")
1157
+ const configs = Object.keys(summary).filter(k => k !== "delta");
1158
+ const configA = configs[0] || "config_a";
1159
+ const configB = configs[1] || "config_b";
1160
+ const labelA = configA.replace(/_/g, " ").replace(/\b\w/g, c => c.toUpperCase());
1161
+ const labelB = configB.replace(/_/g, " ").replace(/\b\w/g, c => c.toUpperCase());
1162
+ const a = summary[configA] || {};
1163
+ const b = summary[configB] || {};
1164
+ const delta = summary.delta || {};
1165
+
1166
+ html += "<thead><tr><th>Metric</th><th>" + escapeHtml(labelA) + "</th><th>" + escapeHtml(labelB) + "</th><th>Delta</th></tr></thead>";
1167
+ html += "<tbody>";
1168
+
1169
+ html += "<tr><td><strong>Pass Rate</strong></td>";
1170
+ html += "<td>" + fmtStat(a.pass_rate, true) + "</td>";
1171
+ html += "<td>" + fmtStat(b.pass_rate, true) + "</td>";
1172
+ html += '<td class="' + deltaClass(delta.pass_rate) + '">' + (delta.pass_rate || "—") + "</td></tr>";
1173
+
1174
+ // Time (only show row if data exists)
1175
+ if (a.time_seconds || b.time_seconds) {
1176
+ html += "<tr><td><strong>Time (s)</strong></td>";
1177
+ html += "<td>" + fmtStat(a.time_seconds, false) + "</td>";
1178
+ html += "<td>" + fmtStat(b.time_seconds, false) + "</td>";
1179
+ html += '<td class="' + deltaClass(delta.time_seconds) + '">' + (delta.time_seconds ? delta.time_seconds + "s" : "—") + "</td></tr>";
1180
+ }
1181
+
1182
+ // Tokens (only show row if data exists)
1183
+ if (a.tokens || b.tokens) {
1184
+ html += "<tr><td><strong>Tokens</strong></td>";
1185
+ html += "<td>" + fmtStat(a.tokens, false) + "</td>";
1186
+ html += "<td>" + fmtStat(b.tokens, false) + "</td>";
1187
+ html += '<td class="' + deltaClass(delta.tokens) + '">' + (delta.tokens || "—") + "</td></tr>";
1188
+ }
1189
+
1190
+ html += "</tbody></table>";
1191
+
1192
+ // Per-eval breakdown (if runs data available)
1193
+ const runs = data.runs || [];
1194
+ if (runs.length > 0) {
1195
+ const evalIds = [...new Set(runs.map(r => r.eval_id))].sort((a, b) => a - b);
1196
+
1197
+ html += "<h3 style='font-family: Poppins, sans-serif; margin-bottom: 0.75rem;'>Per-Eval Breakdown</h3>";
1198
+
1199
+ const hasTime = runs.some(r => r.result && r.result.time_seconds != null);
1200
+ const hasErrors = runs.some(r => r.result && r.result.errors > 0);
1201
+
1202
+ for (const evalId of evalIds) {
1203
+ const evalRuns = runs.filter(r => r.eval_id === evalId);
1204
+ const evalName = evalRuns[0] && evalRuns[0].eval_name ? evalRuns[0].eval_name : "Eval " + evalId;
1205
+
1206
+ html += "<h4 style='font-family: Poppins, sans-serif; margin: 1rem 0 0.5rem; color: var(--text);'>" + escapeHtml(evalName) + "</h4>";
1207
+ html += '<table class="benchmark-table">';
1208
+ html += "<thead><tr><th>Config</th><th>Run</th><th>Pass Rate</th>";
1209
+ if (hasTime) html += "<th>Time (s)</th>";
1210
+ if (hasErrors) html += "<th>Crashes During Execution</th>";
1211
+ html += "</tr></thead>";
1212
+ html += "<tbody>";
1213
+
1214
+ // Group by config and render with average rows
1215
+ const configGroups = [...new Set(evalRuns.map(r => r.configuration))];
1216
+ for (let ci = 0; ci < configGroups.length; ci++) {
1217
+ const config = configGroups[ci];
1218
+ const configRuns = evalRuns.filter(r => r.configuration === config);
1219
+ if (configRuns.length === 0) continue;
1220
+
1221
+ const rowClass = ci === 0 ? "benchmark-row-with" : "benchmark-row-without";
1222
+ const configLabel = config.replace(/_/g, " ").replace(/\b\w/g, c => c.toUpperCase());
1223
+
1224
+ for (const run of configRuns) {
1225
+ const r = run.result || {};
1226
+ const prClass = r.pass_rate >= 0.8 ? "benchmark-delta-positive" : r.pass_rate < 0.5 ? "benchmark-delta-negative" : "";
1227
+ html += '<tr class="' + rowClass + '">';
1228
+ html += "<td>" + configLabel + "</td>";
1229
+ html += "<td>" + run.run_number + "</td>";
1230
+ html += '<td class="' + prClass + '">' + ((r.pass_rate || 0) * 100).toFixed(0) + "% (" + (r.passed || 0) + "/" + (r.total || 0) + ")</td>";
1231
+ if (hasTime) html += "<td>" + (r.time_seconds != null ? r.time_seconds.toFixed(1) : "—") + "</td>";
1232
+ if (hasErrors) html += "<td>" + (r.errors || 0) + "</td>";
1233
+ html += "</tr>";
1234
+ }
1235
+
1236
+ // Average row
1237
+ const rates = configRuns.map(r => (r.result || {}).pass_rate || 0);
1238
+ const avgRate = rates.reduce((a, b) => a + b, 0) / rates.length;
1239
+ const avgPrClass = avgRate >= 0.8 ? "benchmark-delta-positive" : avgRate < 0.5 ? "benchmark-delta-negative" : "";
1240
+ html += '<tr class="benchmark-row-avg ' + rowClass + '">';
1241
+ html += "<td>" + configLabel + "</td>";
1242
+ html += "<td>Avg</td>";
1243
+ html += '<td class="' + avgPrClass + '">' + (avgRate * 100).toFixed(0) + "%</td>";
1244
+ if (hasTime) {
1245
+ const times = configRuns.map(r => (r.result || {}).time_seconds).filter(t => t != null);
1246
+ html += "<td>" + (times.length ? (times.reduce((a, b) => a + b, 0) / times.length).toFixed(1) : "—") + "</td>";
1247
+ }
1248
+ if (hasErrors) html += "<td></td>";
1249
+ html += "</tr>";
1250
+ }
1251
+ html += "</tbody></table>";
1252
+
1253
+ // Per-assertion detail for this eval
1254
+ const runsWithExpectations = {};
1255
+ for (const config of configGroups) {
1256
+ runsWithExpectations[config] = evalRuns.filter(r => r.configuration === config && r.expectations && r.expectations.length > 0);
1257
+ }
1258
+ const hasAnyExpectations = Object.values(runsWithExpectations).some(runs => runs.length > 0);
1259
+ if (hasAnyExpectations) {
1260
+ // Collect all unique assertion texts across all configs
1261
+ const allAssertions = [];
1262
+ const seen = new Set();
1263
+ for (const config of configGroups) {
1264
+ for (const run of runsWithExpectations[config]) {
1265
+ for (const exp of (run.expectations || [])) {
1266
+ if (!seen.has(exp.text)) {
1267
+ seen.add(exp.text);
1268
+ allAssertions.push(exp.text);
1269
+ }
1270
+ }
1271
+ }
1272
+ }
1273
+
1274
+ html += '<table class="benchmark-table" style="margin-top: 0.5rem;">';
1275
+ html += "<thead><tr><th>Assertion</th>";
1276
+ for (const config of configGroups) {
1277
+ const label = config.replace(/_/g, " ").replace(/\b\w/g, c => c.toUpperCase());
1278
+ html += "<th>" + escapeHtml(label) + "</th>";
1279
+ }
1280
+ html += "</tr></thead><tbody>";
1281
+
1282
+ for (const assertionText of allAssertions) {
1283
+ html += "<tr><td>" + escapeHtml(assertionText) + "</td>";
1284
+
1285
+ for (const config of configGroups) {
1286
+ html += "<td>";
1287
+ for (const run of runsWithExpectations[config]) {
1288
+ const exp = (run.expectations || []).find(e => e.text === assertionText);
1289
+ if (exp) {
1290
+ const cls = exp.passed ? "benchmark-delta-positive" : "benchmark-delta-negative";
1291
+ const icon = exp.passed ? "\u2713" : "\u2717";
1292
+ html += '<span class="' + cls + '" title="Run ' + run.run_number + ': ' + escapeHtml(exp.evidence || "") + '">' + icon + "</span> ";
1293
+ } else {
1294
+ html += "— ";
1295
+ }
1296
+ }
1297
+ html += "</td>";
1298
+ }
1299
+ html += "</tr>";
1300
+ }
1301
+ html += "</tbody></table>";
1302
+ }
1303
+ }
1304
+ }
1305
+
1306
+ // Notes
1307
+ if (notes.length > 0) {
1308
+ html += '<div class="benchmark-notes">';
1309
+ html += "<h3>Analysis Notes</h3>";
1310
+ html += "<ul>";
1311
+ for (const note of notes) {
1312
+ html += "<li>" + escapeHtml(note) + "</li>";
1313
+ }
1314
+ html += "</ul></div>";
1315
+ }
1316
+
1317
+ container.innerHTML = html;
1318
+ }
1319
+
1320
+ // ---- Start ----
1321
+ init();
1322
+ renderBenchmark();
1323
+ </script>
1324
+ </body>
1325
+ </html>