adi_dev_workflow 1.3.1 → 1.4.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 (73) hide show
  1. package/frameworks/commands/ministack/generate-tasks.md +212 -212
  2. package/frameworks/commands/sdd/generate-task-plan.md +4 -4
  3. package/frameworks/commands/sdd/generate-tech-direction.md +2 -2
  4. package/frameworks/commands/sdd/generate-tech-spec.md +4 -4
  5. package/frameworks/commands/sdd/generate-tests.md +171 -39
  6. package/frameworks/config/ai-framework-config.yaml +2 -2
  7. package/frameworks/skills/ministack-tasks-expert/SKILL.md +115 -104
  8. package/frameworks/skills/ministack-tech-direction-expert/SKILL.md +21 -6
  9. package/frameworks/skills/prompt-engineer-expert/SKILL.md +10 -7
  10. package/frameworks/skills/sdd-tech-direction-expert/SKILL.md +21 -6
  11. package/frameworks/skills/sdd-tech-spec-expert/SKILL.md +23 -7
  12. package/package.json +1 -1
  13. package/frameworks/agents/qa-validation-expert.md +0 -458
  14. package/frameworks/skills/sdd-task-plan-expert-workspace/iteration-1/benchmark.json +0 -99
  15. package/frameworks/skills/sdd-task-plan-expert-workspace/iteration-1/benchmark.md +0 -64
  16. package/frameworks/skills/sdd-task-plan-expert-workspace/iteration-1/eval-1-happy-path/eval_metadata.json +0 -12
  17. package/frameworks/skills/sdd-task-plan-expert-workspace/iteration-1/eval-1-happy-path/with_skill/grading.json +0 -32
  18. package/frameworks/skills/sdd-task-plan-expert-workspace/iteration-1/eval-1-happy-path/with_skill/outputs/response.md +0 -134
  19. package/frameworks/skills/sdd-task-plan-expert-workspace/iteration-1/eval-1-happy-path/with_skill/outputs/transcript.md +0 -68
  20. package/frameworks/skills/sdd-task-plan-expert-workspace/iteration-1/eval-1-happy-path/with_skill/timing.json +0 -5
  21. package/frameworks/skills/sdd-task-plan-expert-workspace/iteration-1/eval-1-happy-path/without_skill/grading.json +0 -32
  22. package/frameworks/skills/sdd-task-plan-expert-workspace/iteration-1/eval-1-happy-path/without_skill/outputs/response.md +0 -525
  23. package/frameworks/skills/sdd-task-plan-expert-workspace/iteration-1/eval-1-happy-path/without_skill/outputs/transcript.md +0 -30
  24. package/frameworks/skills/sdd-task-plan-expert-workspace/iteration-1/eval-1-happy-path/without_skill/timing.json +0 -5
  25. package/frameworks/skills/sdd-task-plan-expert-workspace/iteration-1/eval-2-spec-simples/eval_metadata.json +0 -12
  26. package/frameworks/skills/sdd-task-plan-expert-workspace/iteration-1/eval-2-spec-simples/with_skill/grading.json +0 -32
  27. package/frameworks/skills/sdd-task-plan-expert-workspace/iteration-1/eval-2-spec-simples/with_skill/outputs/response.md +0 -1126
  28. package/frameworks/skills/sdd-task-plan-expert-workspace/iteration-1/eval-2-spec-simples/with_skill/outputs/transcript.md +0 -131
  29. package/frameworks/skills/sdd-task-plan-expert-workspace/iteration-1/eval-2-spec-simples/with_skill/timing.json +0 -5
  30. package/frameworks/skills/sdd-task-plan-expert-workspace/iteration-1/eval-2-spec-simples/without_skill/grading.json +0 -32
  31. package/frameworks/skills/sdd-task-plan-expert-workspace/iteration-1/eval-2-spec-simples/without_skill/outputs/response.md +0 -452
  32. package/frameworks/skills/sdd-task-plan-expert-workspace/iteration-1/eval-2-spec-simples/without_skill/outputs/transcript.md +0 -78
  33. package/frameworks/skills/sdd-task-plan-expert-workspace/iteration-1/eval-2-spec-simples/without_skill/timing.json +0 -5
  34. package/frameworks/skills/sdd-task-plan-expert-workspace/iteration-1/eval-3-sem-user-stories/eval_metadata.json +0 -12
  35. package/frameworks/skills/sdd-task-plan-expert-workspace/iteration-1/eval-3-sem-user-stories/with_skill/grading.json +0 -32
  36. package/frameworks/skills/sdd-task-plan-expert-workspace/iteration-1/eval-3-sem-user-stories/with_skill/outputs/response.md +0 -101
  37. package/frameworks/skills/sdd-task-plan-expert-workspace/iteration-1/eval-3-sem-user-stories/with_skill/outputs/transcript.md +0 -133
  38. package/frameworks/skills/sdd-task-plan-expert-workspace/iteration-1/eval-3-sem-user-stories/with_skill/timing.json +0 -5
  39. package/frameworks/skills/sdd-task-plan-expert-workspace/iteration-1/eval-3-sem-user-stories/without_skill/grading.json +0 -32
  40. package/frameworks/skills/sdd-task-plan-expert-workspace/iteration-1/eval-3-sem-user-stories/without_skill/outputs/response.md +0 -248
  41. package/frameworks/skills/sdd-task-plan-expert-workspace/iteration-1/eval-3-sem-user-stories/without_skill/outputs/transcript.md +0 -49
  42. package/frameworks/skills/sdd-task-plan-expert-workspace/iteration-1/eval-3-sem-user-stories/without_skill/timing.json +0 -5
  43. package/frameworks/skills/sdd-task-plan-expert-workspace/iteration-1/review.html +0 -1325
  44. package/frameworks/skills/sdd-task-plan-expert-workspace/iteration-2/benchmark.json +0 -94
  45. package/frameworks/skills/sdd-task-plan-expert-workspace/iteration-2/benchmark.md +0 -67
  46. package/frameworks/skills/sdd-task-plan-expert-workspace/iteration-2/eval-1-happy-path/eval_metadata.json +0 -12
  47. package/frameworks/skills/sdd-task-plan-expert-workspace/iteration-2/eval-1-happy-path/with_skill/grading.json +0 -32
  48. package/frameworks/skills/sdd-task-plan-expert-workspace/iteration-2/eval-1-happy-path/with_skill/outputs/response.md +0 -117
  49. package/frameworks/skills/sdd-task-plan-expert-workspace/iteration-2/eval-1-happy-path/with_skill/outputs/transcript.md +0 -91
  50. package/frameworks/skills/sdd-task-plan-expert-workspace/iteration-2/eval-1-happy-path/with_skill/timing.json +0 -1
  51. package/frameworks/skills/sdd-task-plan-expert-workspace/iteration-2/eval-1-happy-path/without_skill/grading.json +0 -32
  52. package/frameworks/skills/sdd-task-plan-expert-workspace/iteration-2/eval-1-happy-path/without_skill/outputs/response.md +0 -694
  53. package/frameworks/skills/sdd-task-plan-expert-workspace/iteration-2/eval-1-happy-path/without_skill/outputs/transcript.md +0 -45
  54. package/frameworks/skills/sdd-task-plan-expert-workspace/iteration-2/eval-1-happy-path/without_skill/timing.json +0 -1
  55. package/frameworks/skills/sdd-task-plan-expert-workspace/iteration-2/eval-2-spec-simples/eval_metadata.json +0 -12
  56. package/frameworks/skills/sdd-task-plan-expert-workspace/iteration-2/eval-2-spec-simples/with_skill/grading.json +0 -32
  57. package/frameworks/skills/sdd-task-plan-expert-workspace/iteration-2/eval-2-spec-simples/with_skill/outputs/response.md +0 -1087
  58. package/frameworks/skills/sdd-task-plan-expert-workspace/iteration-2/eval-2-spec-simples/with_skill/outputs/transcript.md +0 -124
  59. package/frameworks/skills/sdd-task-plan-expert-workspace/iteration-2/eval-2-spec-simples/with_skill/timing.json +0 -1
  60. package/frameworks/skills/sdd-task-plan-expert-workspace/iteration-2/eval-2-spec-simples/without_skill/grading.json +0 -32
  61. package/frameworks/skills/sdd-task-plan-expert-workspace/iteration-2/eval-2-spec-simples/without_skill/outputs/response.md +0 -458
  62. package/frameworks/skills/sdd-task-plan-expert-workspace/iteration-2/eval-2-spec-simples/without_skill/outputs/transcript.md +0 -84
  63. package/frameworks/skills/sdd-task-plan-expert-workspace/iteration-2/eval-2-spec-simples/without_skill/timing.json +0 -1
  64. package/frameworks/skills/sdd-task-plan-expert-workspace/iteration-2/eval-3-sem-user-stories/eval_metadata.json +0 -12
  65. package/frameworks/skills/sdd-task-plan-expert-workspace/iteration-2/eval-3-sem-user-stories/with_skill/grading.json +0 -32
  66. package/frameworks/skills/sdd-task-plan-expert-workspace/iteration-2/eval-3-sem-user-stories/with_skill/outputs/response.md +0 -70
  67. package/frameworks/skills/sdd-task-plan-expert-workspace/iteration-2/eval-3-sem-user-stories/with_skill/outputs/transcript.md +0 -148
  68. package/frameworks/skills/sdd-task-plan-expert-workspace/iteration-2/eval-3-sem-user-stories/with_skill/timing.json +0 -1
  69. package/frameworks/skills/sdd-task-plan-expert-workspace/iteration-2/eval-3-sem-user-stories/without_skill/grading.json +0 -32
  70. package/frameworks/skills/sdd-task-plan-expert-workspace/iteration-2/eval-3-sem-user-stories/without_skill/outputs/response.md +0 -249
  71. package/frameworks/skills/sdd-task-plan-expert-workspace/iteration-2/eval-3-sem-user-stories/without_skill/outputs/transcript.md +0 -80
  72. package/frameworks/skills/sdd-task-plan-expert-workspace/iteration-2/eval-3-sem-user-stories/without_skill/timing.json +0 -1
  73. package/frameworks/skills/sdd-task-plan-expert-workspace/iteration-2/review.html +0 -1325
@@ -1,1325 +0,0 @@
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 \u00e2\u20ac\u201d TASK PLAN para Feature de Usuario\n\n## 1. Feature Identificada\n\nExtraido da secao \"1. Visao Geral Tecnica\" do SPEC_TECH:\n\n- **Feature**: Modulo de Usuario -- Vakinha Burger\n- **Nome normalizado (kebab-case)**: `feature-user`\n- **Versao**: v1\n- **SPEC_TECH**: `docs/feature-user/v1/spec_tech.md`\n- **PRD**: `docs/feature-user/v1/prd.md`\n\n---\n\n## 2. Confirmacao e Proposta de Macro-Fases\n\nObrigado! Vamos iniciar o TASK PLAN para **Modulo de Usuario -- Vakinha Burger**.\n\n### Analise do Codebase (achados criticos)\n\nAntes de propor as fases, realizei a analise obrigatoria do projeto (Passo 1 e Passo 2 do SKILL.md):\n\n**Passo 1 -- Rules lidas:**\n- `CLAUDE.md` (raiz)\n- `.claude/rules/code-conventions.md`\n- `.claude/rules/database.md`\n- `.claude/rules/fx-di.md`\n- `.claude/rules/grpc.md`\n- `.claude/rules/protobuf.md`\n- `.claude/rules/repository.md`\n- `.claude/rules/service.md`\n- `.claude/rules/sqlc.md`\n\n**Passo 2 -- Exploracao do codebase:**\n\nIdentifiquei que **a feature ja esta integralmente implementada** no branch atual (`user-feature`). Todos os arquivos referenciados no SPEC_TECH ja existem e contem o codigo completo:\n\n| Componente | Arquivo | Estado |\n|------------|---------|--------|\n| Identity package | `internal/pkg/identity/identity.go` | JA EXISTE -- Claims, GenerateToken, ParseToken, WithUserID, UserIDFromContext |\n| Identity tests | `internal/pkg/identity/identity_test.go` | JA EXISTE |\n| Auth interceptor | `internal/infra/grpc/interceptors/auth.go` | JA EXISTE -- Login em skipMethods, usa identity.ParseToken, injeta user_id |\n| Auth interceptor tests | `internal/infra/grpc/interceptors/auth_test.go` | JA EXISTE |\n| Migration 002 | `internal/db/migrations/002_add_endereco_usuarios.sql` | JA EXISTE |\n| User repository | `internal/repository/user_repository.go` | JA EXISTE -- Create, GetByID, GetByEmail, Update, modelo User com Address |\n| Repository integration tests | `internal/repository/user_repository_integration_test.go` | JA EXISTE |\n| User service | `internal/service/user_service.go` | JA EXISTE -- CreateUser, Login, GetUserLogged, UpdateUser, todos os erros, todas as validacoes |\n| User service tests | `internal/service/user_service_test.go` | JA EXISTE |\n| User handler | `internal/handler/grpc/user_handler.go` | JA EXISTE -- CreateUser, Login, GetUserLogged, UpdateUser, mapServiceError |\n| User handler tests | `internal/handler/grpc/user_handler_test.go` | JA EXISTE |\n| Config | `internal/infra/config/config.go` | JA EXISTE -- JWTExpirationHours |\n| E2E tests | `internal/e2e/user_e2e_test.go` | JA EXISTE |\n\n**Conclusao da analise**: Como todo o codigo ja existe, o TASK PLAN que seria gerado teria TODAS as tasks com status \"Concluido\". Isso muda significativamente a natureza do plano.\n\n---\n\n### Proposta de Macro-Fases\n\nApesar de a implementacao ja existir, apresento as macro-fases que TERIAM sido utilizadas para decompor esta feature, conforme o SPEC_TECH:\n\n**Fase 1 -- Fundacao e Infraestrutura**\n- Objetivo: Criar os alicerces tecnicos que nao dependem de logica de negocio\n- Escopo: pacote `identity` (JWT), migration 002, configuracao (`JWTExpirationHours`), queries SQLC, geracao de codigo (`make generate`)\n- Tasks estimadas: T1 (identity package), T2 (migration + queries SQLC + make generate), T3 (config)\n\n**Fase 2 -- Camada de Dados**\n- Objetivo: Implementar o acesso a dados completo (repository)\n- Escopo: Adicionar `Address` ao modelo `User`, implementar `GetByEmail()` e `Update()`, atualizar `Create()`\n- Tasks estimadas: T4 (repository)\n\n**Fase 3 -- Logica de Negocio**\n- Objetivo: Implementar todas as regras de negocio no service\n- Escopo: `CreateUser` (validacoes completas), `Login`, `GetUserLogged`, `UpdateUser`, erros de dominio\n- Tasks estimadas: T5 (CreateUser atualizado + Login), T6 (GetUserLogged + UpdateUser)\n\n**Fase 4 -- Camada de Apresentacao**\n- Objetivo: Implementar handlers gRPC e atualizar o contrato proto\n- Escopo: proto atualizado, handlers `Login`, `GetUserLogged`, `UpdateUser`, `mapServiceError`, interceptor atualizado\n- Tasks estimadas: T7 (proto + make generate), T8 (handlers + interceptor)\n\n**Fase 5 -- Testes e Validacao**\n- Objetivo: Garantir cobertura de testes em todas as camadas\n- Escopo: testes unitarios (identity, service, handler, interceptor), testes de integracao (repository, migration), testes E2E\n- Tasks estimadas: T9 (testes unitarios), T10 (testes integracao), T11 (testes E2E)\n\n### Rastreabilidade US -> Fases\n\n| User Story | Fases Envolvidas |\n|------------|-----------------|\n| US-01 (Cadastro) | Fase 1, 2, 3, 4, 5 |\n| US-02 (Login) | Fase 1, 3, 4, 5 |\n| US-03 (Consultar dados) | Fase 1, 3, 4, 5 |\n| US-04 (Atualizar dados) | Fase 2, 3, 4, 5 |\n\n### Paralelismo Identificado\n\n- T1 (identity), T2 (migration/SQLC), T3 (config) podem rodar em **paralelo** (Fase 1)\n- T5 e T6 sao sequenciais (T6 depende de T5 para erros e tipos compartilhados)\n- T7 deve rodar antes de T8 (handlers dependem do proto gerado)\n- T9, T10, T11 podem rodar em **paralelo** (Fase 5), mas todas dependem de T1-T8\n\n---\n\n## Pergunta ao Usuario\n\n> Podemos iniciar a definicao macro das fases conforme proposto acima? Deseja ajustar algo na estrutura de fases, no agrupamento de tasks ou na ordem de dependencias?\n\n### Resposta Simulada (eval -- sem usuario real)\n\nComo esta e uma execucao de avaliacao sem usuario real, assumo que o usuario responderia:\n\n> \"Sim, pode seguir com essas fases. A estrutura esta boa.\"\n\n---\n\n**Nota**: Esta e a PRIMEIRA interacao do processo. Nas proximas interacoes, seriam destrinchadas as tasks fase a fase, com criacao dos arquivos `tasks/TN.md` e, ao final, o `task_plan.md` de referencia. Conforme instrucoes do eval, paro aqui na primeira interacao.\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 'feature-user' da secao 1 do SPEC_TECH e perguntou 'Podemos iniciar a definicao macro das fases conforme proposto acima? Deseja ajustar algo na estrutura de fases?'"}, {"text": "Faz apenas UMA pergunta por vez (nao bombardeia com multiplas perguntas no mesmo turno)", "passed": true, "evidence": "Apenas uma pergunta ao final da primeira interacao, aguardando validacao das macro-fases."}, {"text": "Propoe macro-fases de alto nivel e aguarda validacao antes de criar tasks", "passed": true, "evidence": "Propos 5 macro-fases (Fundacao, Dados, Negocio, Apresentacao, Testes) com 11 tasks estimadas e aguardou confirmacao."}, {"text": "Le as rules do projeto (.claude/rules/ e CLAUDE.md) ANTES de gerar tasks", "passed": true, "evidence": "Transcript mostra 'Passo 1 -- Read ALL project rules' com lista explicita de CLAUDE.md + 8 arquivos .claude/rules/. Executado ANTES da exploracao do codebase (Passo 2)."}, {"text": "NAO inicia execucao automatica de tasks \u00e2\u20ac\u201d apenas apresenta o plano e aguarda", "passed": true, "evidence": "Explicitamente diz 'Esta e a PRIMEIRA interacao do processo. Nas proximas interacoes, seriam destrinchadas as tasks...' \u00e2\u20ac\u201d nao executou nenhuma task."}]}}, {"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 \u00e2\u20ac\u201d Modulo de Usuario v1\n\n## Informacoes Gerais\n\n| Campo | Valor |\n|-------|-------|\n| **Feature** | Modulo de Usuario \u00e2\u20ac\u201d Vakinha Burger |\n| **SPEC_TECH** | `docs/feature-user/v1/spec_tech.md` |\n| **Data** | 2026-03-08 |\n| **Branch** | `user-feature` |\n| **Total de Tasks** | 12 |\n\n---\n\n## Estrategia de Implementacao\n\nA implementacao segue a ordem do fluxo de dependencias do projeto: **Config -> Database -> Identity -> Repository -> Service -> Handler -> Interceptor -> Testes**. Cada task e independente o suficiente para ser validada isoladamente, mas deve ser implementada na ordem sequencial definida.\n\n**Convencao de prioridade:**\n- **P0** \u00e2\u20ac\u201d Bloqueante para tasks seguintes\n- **P1** \u00e2\u20ac\u201d Necessario para completar a feature\n- **P2** \u00e2\u20ac\u201d Qualidade e testes complementares\n\n---\n\n## Tasks\n\n### TASK 01 \u00e2\u20ac\u201d Configuracao: Adicionar `jwt.expiration_hours`\n\n**Prioridade:** P0\n**Tipo:** Modificar arquivos existentes\n\n**Arquivos a modificar:**\n- `configs/config.yaml` \u00e2\u20ac\u201d adicionar `expiration_hours: 24` dentro do bloco `jwt`\n- `internal/infra/config/config.go` \u00e2\u20ac\u201d adicionar campo `JWTExpirationHours int` na struct `Config` e leitura via `v.GetInt(\"jwt.expiration_hours\")`\n\n**Detalhes:**\n1. Em `configs/config.yaml`, adicionar `expiration_hours: 24` sob a chave `jwt`\n2. Em `config.go`, adicionar o campo `JWTExpirationHours int` na struct `Config`\n3. No construtor `NewConfig`, adicionar `v.GetInt(\"jwt.expiration_hours\")` para popular o campo\n\n**Criterio de conclusao:**\n- `Config` carrega `JWTExpirationHours` corretamente a partir do YAML\n- Valor padrao: 24\n- `make build` compila sem erro\n\n**Dependencias:** Nenhuma\n\n---\n\n### TASK 02 \u00e2\u20ac\u201d Migration: Adicionar coluna `endereco` na tabela `usuarios`\n\n**Prioridade:** P0\n**Tipo:** Criar arquivo novo (se nao existir) ou validar existente\n\n**Arquivo:**\n- `internal/db/migrations/002_add_endereco_usuarios.sql`\n\n**Detalhes:**\n1. Verificar se o arquivo ja existe (nota: conforme listagem do codebase, o arquivo `002_add_endereco_usuarios.sql` ja existe)\n2. Validar que o conteudo corresponde ao especificado na SPEC_TECH secao 9.1:\n ```sql\n ALTER TABLE usuarios ADD COLUMN endereco TEXT NOT NULL DEFAULT '';\n ```\n3. Se ja existir com conteudo correto, esta task esta concluida\n\n**Criterio de conclusao:**\n- Migration executa sem erro na inicializacao\n- Coluna `endereco` presente na tabela `usuarios` com default vazio\n\n**Dependencias:** Nenhuma\n\n---\n\n### TASK 03 \u00e2\u20ac\u201d Proto: Atualizar `user.proto` com novos endpoints\n\n**Prioridade:** P0\n**Tipo:** Modificar arquivo existente\n\n**Arquivo a modificar:**\n- `api/proto/v1/user.proto` \u00e2\u20ac\u201d substituicao total conforme SPEC_TECH secao 8.1\n\n**Detalhes:**\n1. Atualizar mensagem `User` para incluir campo `address` (field 4)\n2. Atualizar `CreateUserRequest` para incluir campo `address` (field 4)\n3. Adicionar mensagens: `LoginRequest`, `LoginResponse`, `GetUserLoggedRequest`, `GetUserLoggedResponse`, `UpdateUserRequest` (com campos `optional`), `UpdateUserResponse`\n4. Atualizar servico `UserService` com 4 RPCs: `CreateUser`, `Login`, `GetUserLogged`, `UpdateUser`\n5. Remover `GetUser` (se existir) \u00e2\u20ac\u201d substituido por `GetUserLogged`\n6. Executar `make generate` para regenerar codigo\n\n**Criterio de conclusao:**\n- `make generate` executa sem erros\n- Codigo gerado em `gen/proto/v1/` reflete as novas mensagens e servico\n- `make lint` passa sem erros nos protobufs\n\n**Dependencias:** Nenhuma\n\n---\n\n### TASK 04 \u00e2\u20ac\u201d SQLC: Atualizar queries de usuario\n\n**Prioridade:** P0\n**Tipo:** Modificar arquivo existente\n\n**Arquivo a modificar:**\n- `internal/db/queries/user.sql` \u00e2\u20ac\u201d substituicao total conforme SPEC_TECH secao 9.2\n\n**Detalhes:**\n1. Atualizar `CreateUser` para incluir `endereco`, `data_criacao`, `data_atualizacao` nos campos de INSERT\n2. Manter `GetUserByID :one` \u00e2\u20ac\u201d busca por ID\n3. Adicionar `GetUserByEmail :one` \u00e2\u20ac\u201d busca por email\n4. Adicionar `UpdateUser :one` \u00e2\u20ac\u201d update de `nome`, `email`, `senha_hash`, `endereco`, `data_atualizacao` com `RETURNING *`\n5. Executar `make generate` para regenerar codigo SQLC\n\n**Criterio de conclusao:**\n- `make generate` executa sem erros\n- Codigo gerado em `internal/db/sqlc/` reflete as novas queries\n- Struct `CreateUserParams` inclui campo `Endereco`\n\n**Dependencias:** TASK 02 (migration deve existir para SQLC reconhecer a coluna)\n\n---\n\n### TASK 05 \u00e2\u20ac\u201d Identity: Criar pacote `internal/pkg/identity`\n\n**Prioridade:** P0\n**Tipo:** Criar arquivo novo\n\n**Arquivo a criar:**\n- `internal/pkg/identity/identity.go`\n\n**Detalhes:**\n1. Definir tipo `Claims` com `UserID string`, `Email string` e `jwt.RegisteredClaims`\n2. Implementar `GenerateToken(userID, email, secret string, duration time.Duration) (string, time.Time, error)`\n - Assinatura HS256\n - Preencher `RegisteredClaims{ExpiresAt, IssuedAt}`\n3. Implementar `ParseToken(tokenString, secret string) (*Claims, error)`\n - Validar algoritmo HS256 (rejeitar outros metodos de assinatura)\n4. Implementar `WithUserID(ctx context.Context, userID string) context.Context`\n - Usar tipo `contextKey` unexportado: `type contextKey string`\n - Constante unexportada: `const userIDKey contextKey = \"user_id\"`\n5. Implementar `UserIDFromContext(ctx context.Context) (string, bool)`\n6. Adicionar dependencia `github.com/golang-jwt/jwt/v5` ao `go.mod`\n\n**Criterio de conclusao:**\n- Todas as 4 funcoes publicas implementadas conforme assinaturas da SPEC_TECH secao 10.1\n- `contextKey` e `userIDKey` sao unexportados\n- `CGO_ENABLED=1 go build ./internal/pkg/identity/...` compila sem erro\n\n**Dependencias:** Nenhuma\n\n---\n\n### TASK 06 \u00e2\u20ac\u201d Repository: Estender `UserRepository`\n\n**Prioridade:** P0\n**Tipo:** Modificar arquivo existente\n\n**Arquivo a modificar:**\n- `internal/repository/user_repository.go`\n\n**Detalhes:**\n1. Adicionar campo `Address string` ao struct `User`\n2. Atualizar mapeamento no metodo `Create()`:\n - Incluir `Endereco` nos params SQLC\n - Incluir `Address: sqlcUser.Endereco` no mapeamento de retorno\n3. Atualizar mapeamento no metodo `GetByID()`:\n - Incluir `Address: sqlcUser.Endereco` no mapeamento de retorno\n4. Adicionar metodo `GetByEmail(ctx context.Context, email string) (*User, error)` a interface e implementacao:\n - Chamar `r.queries.GetUserByEmail(ctx, email)`\n - Mapear resultado SQLC -> dominio (incluindo `Address`)\n - Erro: `\"falha ao buscar usuario por email: %w\"`\n5. Adicionar metodo `Update(ctx context.Context, user *User) (*User, error)` a interface e implementacao:\n - Mapear `*User` -> `sqlc.UpdateUserParams` (Nome, Email, SenhaHash, Endereco, DataAtualizacao, ID)\n - Mapear resultado SQLC -> dominio\n - Erro: `\"falha ao atualizar usuario: %w\"`\n6. Mensagens de erro em portugues\n\n**Criterio de conclusao:**\n- Interface `UserRepository` tem 4 metodos: `Create`, `GetByID`, `GetByEmail`, `Update`\n- Todos os mapeamentos incluem `Address`/`Endereco`\n- `CGO_ENABLED=1 go build ./internal/repository/...` compila sem erro\n\n**Dependencias:** TASK 04 (queries SQLC geradas)\n\n---\n\n### TASK 07 \u00e2\u20ac\u201d Service: Estender `UserService`\n\n**Prioridade:** P0\n**Tipo:** Modificar arquivo existente\n\n**Arquivo a modificar:**\n- `internal/service/user_service.go`\n\n**Detalhes:**\n\n**7.1 \u00e2\u20ac\u201d Definir variaveis de erro (10 erros, mensagens em portugues):**\n```go\nvar (\n ErrInvalidName = errors.New(\"nome nao pode estar vazio\")\n ErrInvalidEmail = errors.New(\"email invalido\")\n ErrEmailAlreadyExists = errors.New(\"email ja cadastrado\")\n ErrPasswordTooShort = errors.New(\"senha deve ter no minimo 8 caracteres\")\n ErrAddressRequired = errors.New(\"endereco nao pode estar vazio\")\n ErrInvalidCredentials = errors.New(\"credenciais invalidas\")\n ErrUserNotFound = errors.New(\"usuario nao encontrado\")\n ErrNoFieldsToUpdate = errors.New(\"ao menos um campo deve ser fornecido para atualizacao\")\n ErrCurrentPasswordRequired = errors.New(\"senha atual e obrigatoria quando nova senha e informada\")\n ErrCurrentPasswordWrong = errors.New(\"senha atual incorreta\")\n)\n```\n\n**7.2 \u00e2\u20ac\u201d Definir novos tipos:**\n- Atualizar `CreateUserInput` \u00e2\u20ac\u201d adicionar `Address string`\n- Criar `LoginResult{Token string, ExpiresAt time.Time, User *repository.User}`\n- Criar `UpdateUserInput{Name, Email, Address, NewPassword, CurrentPassword *string}`\n\n**7.3 \u00e2\u20ac\u201d Atualizar construtor:**\n- `NewUserService(repo repository.UserRepository, cfg *config.Config, logger *zap.Logger) UserService`\n- Armazenar `cfg` e `logger` na struct `userService`\n\n**7.4 \u00e2\u20ac\u201d Atualizar interface `UserService` com 4 metodos:**\n- `CreateUser(ctx context.Context, input CreateUserInput) (*repository.User, error)`\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\n**7.5 \u00e2\u20ac\u201d Atualizar `CreateUser`:**\n- Validacoes em ordem: name vazio -> email formato (`net/mail.ParseAddress`) -> password len < 8 -> address vazio\n- Verificar email unico: `repo.GetByEmail(email)` \u00e2\u20ac\u201d se encontrado: `ErrEmailAlreadyExists`\n- Hash: `bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)`\n- Persistir via `repo.Create` incluindo `Address`\n- Log Info: `\"usuario criado com sucesso\"` com `zap.String(\"user_id\", ...)` e `zap.String(\"email\", ...)`\n\n**7.6 \u00e2\u20ac\u201d Implementar `Login`:**\n- `repo.GetByEmail(email)` \u00e2\u20ac\u201d se nao existe: `ErrInvalidCredentials` + Log Warn\n- `bcrypt.CompareHashAndPassword(hash, password)` \u00e2\u20ac\u201d se falha: `ErrInvalidCredentials` + Log Warn\n- `identity.GenerateToken(userID, email, cfg.JWTSecret, time.Duration(cfg.JWTExpirationHours)*time.Hour)`\n- Retornar `LoginResult` com `User.PasswordHash = \"\"`\n- Log Info: `\"login bem-sucedido\"`\n\n**7.7 \u00e2\u20ac\u201d Implementar `GetUserLogged`:**\n- `repo.GetByID(userID)` \u00e2\u20ac\u201d se erro: `ErrUserNotFound`\n- Limpar `user.PasswordHash = \"\"` antes de retornar\n\n**7.8 \u00e2\u20ac\u201d Implementar `UpdateUser`:**\n- Validar ao menos 1 campo nao-nil -> `ErrNoFieldsToUpdate`\n- `repo.GetByID(userID)` -> obter usuario atual\n- Se `input.Email != nil`: `net/mail.ParseAddress` -> `ErrInvalidEmail`; `repo.GetByEmail` -> se existe e ID diferente: `ErrEmailAlreadyExists`\n- Se `input.NewPassword != nil`:\n - `len(*input.NewPassword) < 8` -> `ErrPasswordTooShort`\n - `input.CurrentPassword == nil` -> `ErrCurrentPasswordRequired`\n - `bcrypt.CompareHashAndPassword(currentUser.PasswordHash, *CurrentPassword)` -> `ErrCurrentPasswordWrong`\n - `bcrypt.GenerateFromPassword(*NewPassword, DefaultCost)`\n- Aplicar campos nao-nil sobre `currentUser`\n- `repo.Update(updatedUser)`\n- Limpar `PasswordHash` no retorno\n- Log Info com campos atualizados (nomes dos campos, nao valores)\n\n**Criterio de conclusao:**\n- Interface `UserService` com 4 metodos\n- 10 variaveis de erro definidas\n- Logging em portugues em todos os caminhos relevantes\n- Nenhuma senha, hash ou token logado\n- `CGO_ENABLED=1 go build ./internal/service/...` compila sem erro\n\n**Dependencias:** TASK 05 (identity), TASK 06 (repository), TASK 01 (config)\n\n---\n\n### TASK 08 \u00e2\u20ac\u201d Handler: Estender `UserHandler` gRPC\n\n**Prioridade:** P0\n**Tipo:** Modificar arquivo existente\n\n**Arquivo a modificar:**\n- `internal/handler/grpc/user_handler.go`\n\n**Detalhes:**\n\n**8.1 \u00e2\u20ac\u201d Atualizar `CreateUser`:**\n- Incluir `Address` no mapeamento request -> `CreateUserInput`\n- Resposta: `CreateUserResponse{id, name, email, created_at}` \u00e2\u20ac\u201d sem `PasswordHash`\n\n**8.2 \u00e2\u20ac\u201d Implementar `Login`:**\n- Mapear `LoginRequest{email, password}` para chamada `service.Login`\n- Resposta: `LoginResponse{token, expires_at, id, name, email}`\n- Formatar `expires_at` como string (RFC3339 ou similar)\n\n**8.3 \u00e2\u20ac\u201d Implementar `GetUserLogged`:**\n- Extrair `userID` via `identity.UserIDFromContext(ctx)` \u00e2\u20ac\u201d se ausente: `codes.Unauthenticated`\n- Chamar `service.GetUserLogged(ctx, userID)`\n- Resposta: `GetUserLoggedResponse{User{id, name, email, address, created_at, updated_at}}`\n\n**8.4 \u00e2\u20ac\u201d Implementar `UpdateUser`:**\n- Extrair `userID` via `identity.UserIDFromContext(ctx)` \u00e2\u20ac\u201d se ausente: `codes.Unauthenticated`\n- Construir `UpdateUserInput` com ponteiros (campos `optional` do proto -> `*string`)\n- Chamar `service.UpdateUser(ctx, userID, input)`\n- Resposta: `UpdateUserResponse{User{...}}`\n\n**8.5 \u00e2\u20ac\u201d Remover `GetUser` (se existir) \u00e2\u20ac\u201d substituido por `GetUserLogged`**\n\n**8.6 \u00e2\u20ac\u201d Criar funcao auxiliar `domainUserToProto(user *repository.User) *pb.User`**\n\n**8.7 \u00e2\u20ac\u201d Atualizar `mapServiceError` conforme SPEC_TECH secao 11.2:**\n\n| Erro de Service | gRPC Code |\n|----------------|-----------|\n| `ErrInvalidName`, `ErrInvalidEmail`, `ErrPasswordTooShort`, `ErrAddressRequired`, `ErrNoFieldsToUpdate`, `ErrCurrentPasswordRequired`, `ErrCurrentPasswordWrong` | `codes.InvalidArgument` |\n| `ErrEmailAlreadyExists` | `codes.AlreadyExists` |\n| `ErrInvalidCredentials` | `codes.Unauthenticated` |\n| `ErrUserNotFound` | `codes.NotFound` |\n| Demais erros | `codes.Internal` com mensagem `\"erro interno do servidor\"` |\n\n**Criterio de conclusao:**\n- Handler implementa os 4 RPCs do `UserServiceServer`\n- Mapeamento de erros completo\n- `PasswordHash` nunca exposto em nenhuma response\n- `user_id` extraido do contexto (nao de parametros) nos endpoints protegidos\n- `CGO_ENABLED=1 go build ./internal/handler/grpc/...` compila sem erro\n\n**Dependencias:** TASK 07 (service), TASK 05 (identity), TASK 03 (proto gerado)\n\n---\n\n### TASK 09 \u00e2\u20ac\u201d Interceptor: Atualizar `AuthInterceptor`\n\n**Prioridade:** P0\n**Tipo:** Modificar arquivo existente\n\n**Arquivo a modificar:**\n- `internal/infra/grpc/interceptors/auth.go`\n\n**Detalhes:**\n1. Adicionar `/proto.v1.UserService/Login` em `skipMethods` (junto com `CreateUser` que ja deve estar la)\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(ctx, req)`\n\n**Criterio de conclusao:**\n- `Login` e `CreateUser` sao rotas publicas (sem verificacao de token)\n- `GetUserLogged` e `UpdateUser` exigem token valido\n- `user_id` do JWT e injetado no contexto via `identity.WithUserID`\n- Token invalido/ausente retorna `codes.Unauthenticated`\n- `CGO_ENABLED=1 go build ./internal/infra/grpc/interceptors/...` compila sem erro\n\n**Dependencias:** TASK 05 (identity)\n\n---\n\n### TASK 10 \u00e2\u20ac\u201d Testes Unitarios: Identity + Service + Handler + Interceptor\n\n**Prioridade:** P1\n**Tipo:** Criar + Modificar arquivos\n\n**Arquivos a criar:**\n- `internal/pkg/identity/identity_test.go`\n- `internal/infra/grpc/interceptors/auth_test.go`\n\n**Arquivos a modificar:**\n- `internal/service/user_service_test.go`\n- `internal/handler/grpc/user_handler_test.go`\n\n**Detalhes:**\n\n**10.1 \u00e2\u20ac\u201d Identity tests (`identity_test.go`, 8 cenarios):**\n\n| Teste | Cenario |\n|-------|---------|\n| `TestGenerateToken_Success` | Token nao vazio, `expiresAt` no futuro |\n| `TestGenerateToken_TokenIsParseable` | `ParseToken(token, secret)` sem erro |\n| `TestGenerateToken_ClaimsContainCorrectData` | `claims.UserID` e `claims.Email` corretos |\n| `TestParseToken_WrongSecret` | Erro retornado |\n| `TestParseToken_ExpiredToken` | Erro retornado |\n| `TestParseToken_MalformedToken` | Erro retornado |\n| `TestContextRoundtrip_UserID` | `WithUserID` + `UserIDFromContext` retorna `\"uuid-ctx\", true` |\n| `TestUserIDFromContext_Missing` | Retorna `\"\", false` |\n\n**10.2 \u00e2\u20ac\u201d Service tests \u00e2\u20ac\u201d CreateUser (atualizar existentes + novos, ~9 cenarios):**\n\n| Teste | Cenario |\n|-------|---------|\n| `TestCreateUser_Success` | Atualizar para incluir `Address` |\n| `TestCreateUser_PasswordIsHashed` | Existente \u00e2\u20ac\u201d manter |\n| `TestCreateUser_GeneratesValidUUID` | Existente \u00e2\u20ac\u201d manter |\n| `TestCreateUser_ErrorEmptyName` | Atualizar se necessario |\n| `TestCreateUser_ErrorInvalidEmailFormat` | **Novo** \u00e2\u20ac\u201d email invalido |\n| `TestCreateUser_ErrorPasswordTooShort_7Chars` | **Novo** \u00e2\u20ac\u201d boundary 7 chars |\n| `TestCreateUser_PasswordExactly8Chars` | **Novo** \u00e2\u20ac\u201d boundary 8 chars OK |\n| `TestCreateUser_ErrorEmptyAddress` | **Novo** \u00e2\u20ac\u201d address vazio |\n| `TestCreateUser_ErrorRepositoryFailure` | Existente \u00e2\u20ac\u201d manter |\n\n**10.3 \u00e2\u20ac\u201d Service tests \u00e2\u20ac\u201d Login (6 cenarios):**\n\n| Teste | Cenario |\n|-------|---------|\n| `TestLogin_Success` | `*LoginResult` com `Token` nao vazio, `ExpiresAt` no futuro |\n| `TestLogin_ResultDoesNotExposePasswordHash` | `result.User.PasswordHash == \"\"` |\n| `TestLogin_TokenExpiresAtRespectsConfig` | `result.ExpiresAt` ~= `now + JWTExpirationHours` (tolerancia 5s) |\n| `TestLogin_EmailNotFound_GenericError` | `ErrInvalidCredentials` |\n| `TestLogin_WrongPassword_GenericError` | `ErrInvalidCredentials` |\n| `TestLogin_ErrorRepositoryFailure` | Erro interno propagado |\n\n**10.4 \u00e2\u20ac\u201d Service tests \u00e2\u20ac\u201d GetUserLogged (3 cenarios):**\n\n| Teste | Cenario |\n|-------|---------|\n| `TestGetUserLogged_Success` | `*repository.User` com `PasswordHash:\"\"` |\n| `TestGetUserLogged_UserNotFound` | `ErrUserNotFound` |\n| `TestGetUserLogged_RepositoryError` | Erro interno propagado |\n\n**10.5 \u00e2\u20ac\u201d Service tests \u00e2\u20ac\u201d UpdateUser (13 cenarios):**\n\n| Teste | Cenario |\n|-------|---------|\n| `TestUpdateUser_NameOnly` | Apenas nome atualizado |\n| `TestUpdateUser_EmailOnly` | Email atualizado |\n| `TestUpdateUser_AddressOnly` | Endereco atualizado |\n| `TestUpdateUser_PasswordChange_Success` | Novo hash diferente do anterior |\n| `TestUpdateUser_MultipleFields` | Varios campos aplicados |\n| `TestUpdateUser_NoFieldsToUpdate` | `ErrNoFieldsToUpdate` |\n| `TestUpdateUser_InvalidEmailFormat` | `ErrInvalidEmail` |\n| `TestUpdateUser_EmailAlreadyExists` | `ErrEmailAlreadyExists` |\n| `TestUpdateUser_CurrentPasswordMissing` | `ErrCurrentPasswordRequired` |\n| `TestUpdateUser_WrongCurrentPassword` | `ErrCurrentPasswordWrong` |\n| `TestUpdateUser_NewPasswordTooShort` | `ErrPasswordTooShort` |\n| `TestUpdateUser_UserNotFound` | `ErrUserNotFound` |\n| `TestUpdateUser_RepositoryFailure` | Erro interno propagado |\n\n**10.6 \u00e2\u20ac\u201d Handler tests \u00e2\u20ac\u201d CreateUser (8 cenarios):**\n\n| Teste | Cenario |\n|-------|---------|\n| `TestUserHandler_CreateUser_Success` | Response com `id`, `name`, `email`, `created_at` |\n| `TestUserHandler_CreateUser_ResponseDoesNotExposePasswordHash` | Sem hash na response |\n| `TestUserHandler_CreateUser_EmptyName` | `codes.InvalidArgument` |\n| `TestUserHandler_CreateUser_EmptyEmail` | `codes.InvalidArgument` |\n| `TestUserHandler_CreateUser_ShortPassword` | `codes.InvalidArgument` |\n| `TestUserHandler_CreateUser_EmptyAddress` | `codes.InvalidArgument` |\n| `TestUserHandler_CreateUser_DuplicateEmail` | `codes.AlreadyExists` |\n| `TestUserHandler_CreateUser_InternalError` | `codes.Internal` |\n\n**10.7 \u00e2\u20ac\u201d Handler tests \u00e2\u20ac\u201d Login (3 cenarios):**\n\n| Teste | Cenario |\n|-------|---------|\n| `TestUserHandler_Login_Success` | Response com `token`, `expires_at`, `id`, `name`, `email` |\n| `TestUserHandler_Login_InvalidCredentials` | `codes.Unauthenticated` |\n| `TestUserHandler_Login_InternalError` | `codes.Internal` |\n\n**10.8 \u00e2\u20ac\u201d Handler tests \u00e2\u20ac\u201d GetUserLogged (3 cenarios):**\n\n| Teste | Cenario |\n|-------|---------|\n| `TestUserHandler_GetUserLogged_Success` | Response com dados completos via JWT |\n| `TestUserHandler_GetUserLogged_MissingUserID` | `codes.Unauthenticated` |\n| `TestUserHandler_GetUserLogged_NotFound` | `codes.NotFound` |\n\n**10.9 \u00e2\u20ac\u201d Handler tests \u00e2\u20ac\u201d UpdateUser (10 cenarios):**\n\n| Teste | Cenario |\n|-------|---------|\n| `TestUserHandler_UpdateUser_NameOnly` | Response com `name` atualizado |\n| `TestUserHandler_UpdateUser_EmailOnly` | Response com email atualizado |\n| `TestUserHandler_UpdateUser_PasswordChange` | Response com sucesso |\n| `TestUserHandler_UpdateUser_MultipleFields` | Todos os campos atualizados |\n| `TestUserHandler_UpdateUser_NoFields` | `codes.InvalidArgument` |\n| `TestUserHandler_UpdateUser_EmailAlreadyExists` | `codes.AlreadyExists` |\n| `TestUserHandler_UpdateUser_CurrentPasswordMissing` | `codes.InvalidArgument` |\n| `TestUserHandler_UpdateUser_WrongCurrentPassword` | `codes.InvalidArgument` |\n| `TestUserHandler_UpdateUser_NewPasswordTooShort` | `codes.InvalidArgument` |\n| `TestUserHandler_UpdateUser_UserNotFound` | `codes.NotFound` |\n\n**10.10 \u00e2\u20ac\u201d Interceptor tests (`auth_test.go`, 3 cenarios):**\n\n| Teste | Cenario |\n|-------|---------|\n| `TestAuthInterceptor_ValidToken_InjectsUserID` | Handler recebe ctx com `identity.UserIDFromContext(ctx) == \"uuid-123\"` |\n| `TestAuthInterceptor_SkipsLogin` | `/Login` e `/CreateUser` nao exigem token (table-driven) |\n| `TestAuthInterceptor_ProtectedMethodsRequireAuth` | `GetUserLogged` e `UpdateUser` sem token retornam `Unauthenticated` (table-driven) |\n\n**10.11 \u00e2\u20ac\u201d Atualizar mocks:**\n- `MockUserService` \u00e2\u20ac\u201d adicionar `Login`, `GetUserLogged`, `UpdateUser`\n- `MockUserRepository` \u00e2\u20ac\u201d adicionar `GetByEmail`, `Update`\n\n**Criterio de conclusao:**\n- `CGO_ENABLED=1 go test ./internal/pkg/identity/... -v` \u00e2\u20ac\u201d todos passam\n- `CGO_ENABLED=1 go test ./internal/service/... -v` \u00e2\u20ac\u201d todos passam\n- `CGO_ENABLED=1 go test ./internal/handler/grpc/... -v` \u00e2\u20ac\u201d todos passam\n- `CGO_ENABLED=1 go test ./internal/infra/grpc/interceptors/... -v` \u00e2\u20ac\u201d todos passam\n\n**Dependencias:** TASK 05-09\n\n---\n\n### TASK 11 \u00e2\u20ac\u201d Testes de Integracao: Repository + Migration\n\n**Prioridade:** P1\n**Tipo:** Criar arquivo novo\n\n**Arquivo a criar:**\n- `internal/repository/user_repository_integration_test.go`\n\n**Detalhes:**\n\n**11.1 \u00e2\u20ac\u201d Setup:**\n```go\nfunc setupTestDB(t *testing.T) *sql.DB {\n t.Helper()\n db, err := database.NewDatabase(&config.Config{DatabasePath: filepath.Join(t.TempDir(), \"test.db\")})\n require.NoError(t, err)\n t.Cleanup(func() { db.Close() })\n return db\n}\n```\n\n**11.2 \u00e2\u20ac\u201d Cenarios de Repository (12 testes):**\n\n| Teste | Cenario |\n|-------|---------|\n| `TestUserRepository_Create_Success` | Todos os campos persistidos incluindo `Address` |\n| `TestUserRepository_CreateAndGetByID` | Dados identicos ao inserido |\n| `TestUserRepository_GetByEmail` | Retorna user correto com `Address` |\n| `TestUserRepository_Create_DuplicateEmail` | Erro contendo `\"UNIQUE constraint failed\"` |\n| `TestUserRepository_GetByID_NotFound` | Erro contendo `\"nao encontrado\"` |\n| `TestUserRepository_GetByEmail_NotFound` | Erro contendo `\"nao encontrado\"` |\n| `TestUserRepository_Update_NameOnly` | `Name == \"Novo Nome\"`, outros inalterados |\n| `TestUserRepository_Update_Email` | `Email == \"novo@x.com\"` |\n| `TestUserRepository_Update_PasswordHash` | `PasswordHash == novoHash` |\n| `TestUserRepository_Update_Address` | `Address == \"Nova Rua, 99\"` |\n| `TestUserRepository_Update_UpdatedAtChanges` | `UpdatedAt` posterior ao `CreatedAt` |\n| `TestUserRepository_Create_GeneratesValidUUID` | `uuid.Parse(result.ID)` sem erro |\n\n**11.3 \u00e2\u20ac\u201d Testes de migracao (em `internal/infra/database/sqlite_test.go`):**\n\n| Teste | Cenario |\n|-------|---------|\n| `TestRunMigrations_TableSchema_IncludesEndereco` | `PRAGMA table_info(usuarios)` lista `endereco` com tipo `TEXT` |\n| `TestRunMigrations_EnderecocolumnDefaultEmpty` | INSERT sem `endereco` -> SELECT retorna `\"\"` |\n| `TestRunMigrations_IdempotentWithEndereco` | Segunda `NewDatabase` nao retorna erro |\n\n**Criterio de conclusao:**\n- `CGO_ENABLED=1 go test ./internal/repository/... -v` \u00e2\u20ac\u201d todos passam\n- Testes usam banco em diretorio temporario (sem efeitos colaterais)\n\n**Dependencias:** TASK 02, 04, 06\n\n---\n\n### TASK 12 \u00e2\u20ac\u201d Testes E2E: Fluxos completos\n\n**Prioridade:** P2\n**Tipo:** Criar arquivo novo\n\n**Arquivo a criar:**\n- `internal/e2e/user_e2e_test.go`\n\n**Detalhes:**\n\n**12.1 \u00e2\u20ac\u201d Setup:**\n- `setupE2EServer(t)` \u00e2\u20ac\u201d servidor gRPC em porta efemera (`:0`)\n- Banco SQLite em `t.TempDir()`\n- Config, DB, Repo, Service, Handler, gRPC Server montados manualmente ou via FX\n- Retorna `pb.UserServiceClient` + funcao de cleanup\n\n**12.2 \u00e2\u20ac\u201d Cenarios (5 fluxos E2E):**\n\n| Teste | Fluxo |\n|-------|-------|\n| `TestE2E_CreateUser_FullFlow` | `CreateUser` com dados validos -> verificar response: UUID valido, `name`, `email`, `created_at` presentes, sem token (RN-6) |\n| `TestE2E_LoginAndGetUserLogged_FullFlow` | `CreateUser` -> `Login` -> verificar token nao vazio -> `GetUserLogged` com Bearer token -> verificar dados completos (`id`, `name`, `email`, `address`) |\n| `TestE2E_UpdateUser_PartialUpdate` | `CreateUser` -> `Login` -> `UpdateUser{name: \"Novo Nome\"}` com Bearer -> `GetUserLogged` -> confirmar `name` atualizado, demais inalterados |\n| `TestE2E_ProtectedRoutes_RequireAuth` | `GetUserLogged` sem token -> `codes.Unauthenticated`; `UpdateUser` sem token -> `codes.Unauthenticated`; `CreateUser` e `Login` funcionam sem token |\n| `TestE2E_UpdateUser_PasswordChangeAndReauth` | `CreateUser` -> `Login` (senha original) -> `UpdateUser{new_password, current_password}` -> `Login` nova senha -> sucesso; `Login` senha antiga -> `codes.Unauthenticated` |\n\n**Criterio de conclusao:**\n- `CGO_ENABLED=1 go test ./internal/e2e/... -v` \u00e2\u20ac\u201d todos passam\n- Testes isolados (banco temporario, porta efemera)\n\n**Dependencias:** TASK 01-09 (implementacao completa)\n\n---\n\n## Ordem de Execucao\n\n```\nFase 1 \u00e2\u20ac\u201d Fundacao (paralelas):\n TASK 01 (Config)\n TASK 02 (Migration)\n TASK 03 (Proto + make generate)\n TASK 05 (Identity)\n\nFase 2 \u00e2\u20ac\u201d Dados:\n TASK 04 (SQLC queries + make generate) [depende: TASK 02]\n\nFase 3 \u00e2\u20ac\u201d Camadas de negocio:\n TASK 06 (Repository) [depende: TASK 04]\n TASK 09 (Interceptor) [depende: TASK 05]\n\nFase 4 \u00e2\u20ac\u201d Logica e apresentacao:\n TASK 07 (Service) [depende: TASK 01, 05, 06]\n TASK 08 (Handler) [depende: TASK 03, 05, 07]\n\nFase 5 \u00e2\u20ac\u201d Testes:\n TASK 10 (Unitarios) [depende: TASK 05-09]\n TASK 11 (Integracao) [depende: TASK 02, 04, 06]\n TASK 12 (E2E) [depende: TASK 01-09]\n```\n\n---\n\n## Comandos de Validacao\n\n| Fase | Comando |\n|------|---------|\n| Apos TASK 01 | `make build` |\n| Apos TASK 03 | `make generate && make lint` |\n| Apos TASK 04 | `make generate` |\n| Apos TASK 05 | `CGO_ENABLED=1 go build ./internal/pkg/identity/...` |\n| Apos TASK 06 | `CGO_ENABLED=1 go build ./internal/repository/...` |\n| Apos TASK 07 | `CGO_ENABLED=1 go build ./internal/service/...` |\n| Apos TASK 08 | `CGO_ENABLED=1 go build ./internal/handler/grpc/...` |\n| Apos TASK 09 | `CGO_ENABLED=1 go build ./internal/infra/grpc/interceptors/...` |\n| Apos TASK 10 | `CGO_ENABLED=1 go test ./internal/pkg/identity/... ./internal/service/... ./internal/handler/grpc/... ./internal/infra/grpc/interceptors/... -v` |\n| Apos TASK 11 | `CGO_ENABLED=1 go test ./internal/repository/... -v` |\n| Apos TASK 12 | `CGO_ENABLED=1 go test ./internal/e2e/... -v` |\n| **Validacao final** | `make build && CGO_ENABLED=1 go test ./... -v` |\n\n---\n\n## Rastreabilidade SPEC_TECH -> Tasks\n\n| Secao SPEC_TECH | Task(s) |\n|-----------------|---------|\n| 4.1 Tabela usuarios | TASK 02 |\n| 4.2 Modelos Go | TASK 06, 07 |\n| 4.3 JWT Claims | TASK 05 |\n| 5.2 US-01 CreateUser | TASK 03, 04, 06, 07, 08 |\n| 5.3 US-02 Login | TASK 03, 05, 07, 08 |\n| 5.4 US-03 GetUserLogged | TASK 03, 05, 07, 08, 09 |\n| 5.5 US-04 UpdateUser | TASK 03, 04, 07, 08, 09 |\n| 6 Regras de Negocio (RN-1 a RN-12) | TASK 07 |\n| 8 API Proto | TASK 03 |\n| 9 Persistencia | TASK 02, 04 |\n| 10.1 Identity | TASK 05 |\n| 10.2 Interceptor | TASK 09 |\n| 10.3 bcrypt | TASK 07 |\n| 11 Tratamento de Erros | TASK 07, 08 |\n| 12 Configuracao | TASK 01 |\n| 13 Logging | TASK 07 |\n| 14.1 Testes Unitarios | TASK 10 |\n| 14.2 Testes Integracao | TASK 11 |\n| 14.3 Testes E2E | TASK 12 |\n\n---\n\n## Rastreabilidade Criterios de Aceite -> Tasks\n\n| CA | Tasks de Implementacao | Task de Teste |\n|----|----------------------|---------------|\n| CA-01 (Cadastro com sucesso) | TASK 03, 04, 06, 07, 08 | TASK 10, 11, 12 |\n| CA-02 (Email unico) | TASK 06, 07, 08 | TASK 10, 11, 12 |\n| CA-03 (Formato email) | TASK 07, 08 | TASK 10 |\n| CA-04 (Senha >= 8) | TASK 07, 08 | TASK 10 |\n| CA-05 (Nome obrigatorio) | TASK 07, 08 | TASK 10 |\n| CA-06 (Endereco obrigatorio) | TASK 07, 08 | TASK 10 |\n| CA-07 (Sem auto-login) | TASK 07, 08 | TASK 10, 12 |\n| CA-08 (Login retorna token) | TASK 05, 07, 08 | TASK 10, 12 |\n| CA-09 (Erro generico auth) | TASK 07, 08 | TASK 10, 12 |\n| CA-10 (Consultar dados) | TASK 07, 08, 09 | TASK 10, 11, 12 |\n| CA-11 (Rotas protegidas) | TASK 09 | TASK 10, 12 |\n| CA-12 (Atualizacao parcial) | TASK 07, 08 | TASK 10, 11, 12 |\n| CA-13 (Email unico update) | TASK 07, 08 | TASK 10 |\n| CA-14 (Confirmar senha atual) | TASK 07, 08 | TASK 10, 12 |\n| CA-15 (Nova senha >= 8) | TASK 07, 08 | TASK 10 |\n| CA-16 (Nenhum campo) | TASK 07, 08 | TASK 10 |\n\n---\n\n## Resumo\n\n| Metrica | Valor |\n|---------|-------|\n| Total de tasks | 12 |\n| Tasks P0 (bloqueantes) | 9 |\n| Tasks P1 (necessarias) | 2 |\n| Tasks P2 (complementares) | 1 |\n| Arquivos a criar | 4 (`identity.go`, `identity_test.go`, `auth_test.go`, `user_e2e_test.go`, `user_repository_integration_test.go`) |\n| Arquivos a modificar | 7 (`config.yaml`, `config.go`, `user.proto`, `user.sql`, `user_repository.go`, `user_service.go`, `user_handler.go`, `auth.go`, `user_service_test.go`, `user_handler_test.go`) |\n| Arquivos gerados (nao tocar) | `gen/proto/v1/`, `internal/db/sqlc/` |\n| User Stories cobertas | US-01, US-02, US-03, US-04 |\n| Criterios de Aceite cobertos | CA-01 a CA-16 |\n| Regras de Negocio cobertas | RN-1 a RN-12 |\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": "Gerou o task plan completo com 12 tasks sem confirmar o nome da feature com o usuario. Identificou 'Modulo de Usuario v1' mas nao perguntou se estava correto."}, {"text": "Faz apenas UMA pergunta por vez (nao bombardeia com multiplas perguntas no mesmo turno)", "passed": false, "evidence": "Nenhuma pergunta foi feita ao usuario. O output completo (695 linhas) foi gerado de uma vez sem interacao."}, {"text": "Propoe macro-fases de alto nivel e aguarda validacao antes de criar tasks", "passed": false, "evidence": "Pulou diretamente para tasks detalhadas (TASK 01-12) sem propor macro-fases primeiro e aguardar validacao."}, {"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 arquivos de implementacao. Nenhuma leitura de .claude/rules/ ou CLAUDE.md foi registrada."}, {"text": "NAO inicia execucao automatica de tasks \u00e2\u20ac\u201d apenas apresenta o plano e aguarda", "passed": true, "evidence": "Apresentou apenas o plano sem executar nenhuma task."}]}}, {"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": "# Simulacao Completa do Fluxo TASK PLAN \u00e2\u20ac\u201d Adicionar Telefone ao Usuario\n\n---\n\n## Passo 1: Receber SPEC_TECH e Extrair Nome\n\n**Nome extraido da secao \"1. Identificacao\":** Adicionar Telefone ao Modulo de Usuario\n\n**Pergunta que eu faria ao usuario:**\n\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---\n\n## Passo 2: Analise Obrigatoria do Projeto (Resumo)\n\n### Rules Lidas\n- `CLAUDE.md` \u00e2\u20ac\u201d arquitetura em camadas, comandos, convencoes\n- `.claude/rules/code-conventions.md` \u00e2\u20ac\u201d codigo em ingles, banco/logs/erros em portugues\n- `.claude/rules/database.md` \u00e2\u20ac\u201d SQLite WAL, migracoes imutaveis, nomenclatura PT-BR\n- `.claude/rules/fx-di.md` \u00e2\u20ac\u201d Uber FX para DI\n- `.claude/rules/grpc.md` \u00e2\u20ac\u201d handlers, mapeamento de erros, registro de handlers\n- `.claude/rules/protobuf.md` \u00e2\u20ac\u201d Buf, convencoes de go_package, mensagens\n- `.claude/rules/repository.md` \u00e2\u20ac\u201d interface + impl nao exportada, UUID no repo, mapeamento PT->EN\n- `.claude/rules/service.md` \u00e2\u20ac\u201d validacao, logging, erros exportados\n- `.claude/rules/sqlc.md` \u00e2\u20ac\u201d queries editaveis em `internal/db/sqlc/queries/`, codigo gerado NAO editar\n\n### Codebase Explorado \u00e2\u20ac\u201d O que ja existe\n\n| Camada | Arquivo | Estado Atual |\n|--------|---------|-------------|\n| Migration 1 | `internal/db/migrations/001_create_usuarios.sql` | Tabela `usuarios` com id, nome, email, senha_hash, data_criacao, data_atualizacao |\n| Migration 2 | `internal/db/migrations/002_add_endereco_usuarios.sql` | `ALTER TABLE usuarios ADD COLUMN endereco TEXT NOT NULL DEFAULT ''` |\n| SQLC Queries | `internal/db/sqlc/queries/user.sql` | CreateUser, GetUserByID, GetUserByEmail, UpdateUser \u00e2\u20ac\u201d **sem coluna `telefone`** |\n| SQLC Config | `internal/db/sqlc/sqlc.yaml` | Engine SQLite, queries em `queries/`, schema em `../migrations/` |\n| Repository | `internal/repository/user_repository.go` | Struct `User` com campos ID, Name, Email, PasswordHash, Address, CreatedAt, UpdatedAt. Interface com Create, GetByID, GetByEmail, Update. Mapeamento via `toUser()` |\n| Service | `internal/service/user_service.go` | `CreateUserInput` com Name, Email, Password, Address. `UpdateUserInput` com Name*, Email*, Address*, NewPassword*, CurrentPassword*. Validacoes e regras de negocio |\n| Handler | `internal/handler/grpc/user_handler.go` | Handlers CreateUser, Login, GetUserLogged, UpdateUser. Funcao `domainUserToProto()` e `mapServiceError()` |\n| Handler FX | `internal/handler/grpc/fx.go` | Registra UserHandler como pb.UserServiceServer |\n| Proto | `api/proto/v1/user.proto` | Mensagem User com id(1), name(2), email(3), address(4), created_at(5), updated_at(6). CreateUserRequest com name(1), email(2), password(3), address(4). UpdateUserRequest com optional name(1), email(2), address(3), new_password(4), current_password(5) |\n| gRPC Server | `internal/infra/grpc/server.go` | Registra UserServiceServer |\n| Testes Service | `internal/service/user_service_test.go` | Testes table-driven com mock de UserRepository via testify/mock |\n| Testes Handler | `internal/handler/grpc/user_handler_test.go` | Mock de UserService, testes de todos os endpoints |\n| Testes Repository | `internal/repository/user_repository_integration_test.go` | Testes de integracao com SQLite in-memory |\n\n### Descobertas Importantes para o Planejamento\n\n1. **Proxima migracao sera `003_add_telefone_usuarios.sql`** (sequencial apos 002)\n2. **Queries SQLC precisam ser atualizadas** em `internal/db/sqlc/queries/user.sql` \u00e2\u20ac\u201d depois rodar `make sqlc`\n3. **Repository struct `User`** precisa de novo campo `Phone string`\n4. **Service `CreateUserInput`** e `UpdateUserInput`** precisam de campo `Phone`\n5. **Proto `user.proto`**: campo `phone` sera field number `7` na mensagem `User`, `5` em `CreateUserRequest`, e `6` em `UpdateUserRequest`\n6. **Mock de repository** em `user_service_test.go` precisa do campo `Phone`\n7. **Mock de service** em `user_handler_test.go` precisa do campo `Phone` no input\n8. **Nenhum campo `telefone` existe atualmente** em qualquer camada \u00e2\u20ac\u201d tudo precisa ser criado do zero\n9. **Arquivo `configs/config.yaml`** nao precisa de alteracao (nao ha skip_methods novo)\n10. **Nota sobre nomenclatura de migracao**: o projeto usa `001_create_usuarios.sql` sem `.up.sql`/`.down.sql` \u00e2\u20ac\u201d apenas arquivo unico\n\n---\n\n## Passo 3: Proposta de Fases\n\n**Pergunta que eu faria ao usuario:**\n\n> Com base na analise do SPEC_TECH e do codebase, proponho as seguintes macro-fases:\n>\n> **Fase 1 \u00e2\u20ac\u201d Banco de Dados e Queries (Fundacao)**\n> - Nova migracao para adicionar coluna `telefone`\n> - Atualizar queries SQLC para incluir `telefone`\n> - Executar `make sqlc` para regenerar codigo\n>\n> **Fase 2 \u00e2\u20ac\u201d Camadas de Negocio (Repository + Service)**\n> - Adicionar campo `Phone` ao modelo e mapeamento no repository\n> - Adicionar campo `Phone` nos inputs do service e validacao\n>\n> **Fase 3 \u00e2\u20ac\u201d Interface gRPC (Proto + Handler)**\n> - Atualizar `user.proto` com campo `phone`\n> - Executar `make proto` para regenerar\n> - Atualizar handler para mapear campo `phone`\n>\n> **Fase 4 \u00e2\u20ac\u201d Testes**\n> - Atualizar testes existentes em todas as camadas\n> - Adicionar testes especificos para o campo telefone\n>\n> Podemos prosseguir com esta estrutura de fases?\n\n**Resposta simulada do usuario:** Sim, aprovado. Pode destrinchar as tasks.\n\n---\n\n## Passo 4: Destrinchar Tasks \u00e2\u20ac\u201d Fase por Fase\n\n### Pergunta Fase 1:\n\n> Podemos destrinchar as tasks da Fase 1 \u00e2\u20ac\u201d Banco de Dados e Queries?\n\n**Resposta simulada:** Sim.\n\n### Pergunta Fase 2:\n\n> Podemos destrinchar as tasks da Fase 2 \u00e2\u20ac\u201d Camadas de Negocio?\n\n**Resposta simulada:** Sim.\n\n### Pergunta Fase 3:\n\n> Podemos destrinchar as tasks da Fase 3 \u00e2\u20ac\u201d Interface gRPC?\n\n**Resposta simulada:** Sim.\n\n### Pergunta Fase 4:\n\n> Podemos destrinchar as tasks da Fase 4 \u00e2\u20ac\u201d Testes?\n\n**Resposta simulada:** Sim.\n\n---\n\n## Passo 5: Tasks Individuais (conteudo completo)\n\nAbaixo estao as tasks completas que seriam salvas em `docs/telefone-usuario/v1/tasks/TN.md`:\n\n---\n\n# T1.md \u00e2\u20ac\u201d Criar migracao para adicionar coluna telefone\n\n## 1. Identificacao\n- **ID**: T1\n- **Nome da Task**: Criar migracao para adicionar coluna telefone na tabela usuarios\n- **Responsavel**: \u00e2\u20ac\u201d\n- **Status**: A Fazer\n- **Fase**: Fase 1 \u00e2\u20ac\u201d Banco de Dados e Queries\n- **Dependencias**: Nenhuma\n- **User Stories Relacionadas**: US-10\n\n---\n\n## 2. Objetivo da Task\nCriar nova migracao SQL que adiciona a coluna `telefone` (TEXT, opcional/nullable) a tabela `usuarios`.\n\n---\n\n## 3. Descricao Detalhada\n\nCriar o arquivo `internal/db/migrations/003_add_telefone_usuarios.sql` com o seguinte conteudo:\n\n```sql\nALTER TABLE usuarios ADD COLUMN telefone TEXT;\n```\n\n**Decisoes tecnicas:**\n- A coluna `telefone` e `TEXT` sem `NOT NULL`, tornando-a opcional (nullable)\n- Seguir o padrao do projeto: nome do arquivo sequencial (`003_`), descritivo, sem `.up.sql`/`.down.sql` (projeto usa arquivo unico de migracao)\n- Coluna em portugues conforme convencao do projeto (`telefone`, nao `phone`)\n- Sem valor DEFAULT, pois o campo e opcional e usuarios existentes terao `NULL`\n\n**IMPORTANTE**: Nao editar migracoes existentes (001, 002). Apenas criar nova.\n\n---\n\n## 4. Aceite Tecnico (criterios objetivos)\n- [ ] Arquivo `003_add_telefone_usuarios.sql` criado em `internal/db/migrations/`\n- [ ] SQL sintaticamente correto para SQLite\n- [ ] Coluna `telefone` e TEXT nullable (sem NOT NULL)\n- [ ] Migracao nao altera tabelas existentes alem de adicionar a coluna\n- [ ] Migracao aplica sem erro em banco limpo (junto com 001 e 002)\n- [ ] Nenhuma migracao existente foi editada\n\n---\n\n## 5. Arquivos Impactados\n\n### 5.1 Arquivos a Criar\n| Arquivo | Descricao |\n|---------|-----------|\n| `internal/db/migrations/003_add_telefone_usuarios.sql` | Migracao ALTER TABLE para adicionar coluna telefone |\n\n### 5.2 Arquivos a Modificar\n| Arquivo | Modificacao |\n|---------|------------|\n| Nenhum | \u00e2\u20ac\u201d |\n\n### 5.3 Arquivos de Referencia\n| Arquivo | Motivo da Consulta |\n|---------|-------------------|\n| `internal/db/migrations/001_create_usuarios.sql` | Referencia de schema existente |\n| `internal/db/migrations/002_add_endereco_usuarios.sql` | Referencia de padrao de ALTER TABLE no projeto |\n\n---\n\n## 6. Testes\n\n### 6.1 Testes Unitarios\n- N/A \u00e2\u20ac\u201d migracao SQL nao possui logica testavel unitariamente\n\n### 6.2 Testes de Integracao\n- [ ] Teste: verificar que a coluna `telefone` existe na tabela `usuarios` apos migracao (PRAGMA table_info) \u00e2\u20ac\u201d adicionar em `internal/repository/user_repository_integration_test.go` seguindo o padrao de `TestRunMigrations_TableSchema_IncludesEndereco`\n- [ ] Teste: verificar que a coluna `telefone` aceita NULL (inserir usuario sem telefone e consultar)\n- [ ] Teste: verificar que a coluna `telefone` aceita valor TEXT (inserir usuario com telefone e consultar)\n\n### 6.3 Testes E2E\n- N/A \u00e2\u20ac\u201d sera coberto nas tasks de camadas superiores\n\n### 6.4 Cenarios de Erro\n- [ ] Cenario: migracao aplicada em banco que ja tem a coluna (idempotencia via golang-migrate)\n\n---\n\n## 7. Notas / Observacoes\n- O padrao do projeto para migracoes usa arquivo unico (sem separacao up/down), conforme observado em `001_create_usuarios.sql` e `002_add_endereco_usuarios.sql`\n- A migracao 002 usou `NOT NULL DEFAULT ''` para `endereco` (campo obrigatorio). Para `telefone` o SPEC define como opcional, portanto `TEXT` sem `NOT NULL`\n\n---\n\n## 8. Checklist Final\n- [ ] Implementada conforme SPEC\n- [ ] Testes de integracao criados/atualizados\n- [ ] Aceite tecnico atendido\n- [ ] Revisada\n- [ ] Integrada a branch principal\n\n---\n---\n\n# T2.md \u00e2\u20ac\u201d Atualizar queries SQLC para incluir coluna telefone\n\n## 1. Identificacao\n- **ID**: T2\n- **Nome da Task**: Atualizar queries SQLC para incluir coluna telefone\n- **Responsavel**: \u00e2\u20ac\u201d\n- **Status**: A Fazer\n- **Fase**: Fase 1 \u00e2\u20ac\u201d Banco de Dados e Queries\n- **Dependencias**: T1\n- **User Stories Relacionadas**: US-10\n\n---\n\n## 2. Objetivo da Task\nAtualizar as queries `CreateUser` e `UpdateUser` no arquivo de queries SQLC para incluir a coluna `telefone`, e regenerar o codigo SQLC.\n\n---\n\n## 3. Descricao Detalhada\n\nEditar o arquivo `internal/db/sqlc/queries/user.sql`:\n\n**Query `CreateUser`** \u00e2\u20ac\u201d adicionar `telefone` na lista de colunas e valores:\n```sql\n-- name: CreateUser :one\nINSERT INTO usuarios (id, nome, email, senha_hash, endereco, telefone, data_criacao, data_atualizacao)\nVALUES (?, ?, ?, ?, ?, ?, ?, ?)\nRETURNING *;\n```\n\n**Query `UpdateUser`** \u00e2\u20ac\u201d adicionar `telefone` no SET:\n```sql\n-- name: UpdateUser :one\nUPDATE usuarios\nSET nome = ?,\n email = ?,\n senha_hash = ?,\n endereco = ?,\n telefone = ?,\n data_atualizacao = ?\nWHERE id = ?\nRETURNING *;\n```\n\n**Queries `GetUserByID` e `GetUserByEmail`** \u00e2\u20ac\u201d usam `SELECT *`, portanto ja incluirao `telefone` automaticamente apos a migracao. Nao precisam de alteracao.\n\nApos editar, executar `make sqlc` para regenerar o codigo em `internal/db/sqlc/`.\n\n**IMPORTANTE**: NAO editar arquivos em `internal/db/sqlc/` diretamente \u00e2\u20ac\u201d apenas os arquivos em `internal/db/sqlc/queries/`.\n\n---\n\n## 4. Aceite Tecnico (criterios objetivos)\n- [ ] Query `CreateUser` inclui coluna `telefone` e parametro correspondente\n- [ ] Query `UpdateUser` inclui `telefone` no SET\n- [ ] `make sqlc` executa sem erros\n- [ ] Struct `Usuario` gerada em `internal/db/sqlc/` cont\u00c3\u00a9m campo `Telefone`\n- [ ] Params `CreateUserParams` inclui campo `Telefone`\n- [ ] Params `UpdateUserParams` inclui campo `Telefone`\n- [ ] Queries SELECT continuam funcionando (retornando `telefone`)\n\n---\n\n## 5. Arquivos Impactados\n\n### 5.1 Arquivos a Criar\n| Arquivo | Descricao |\n|---------|-----------|\n| Nenhum | \u00e2\u20ac\u201d |\n\n### 5.2 Arquivos a Modificar\n| Arquivo | Modificacao |\n|---------|------------|\n| `internal/db/sqlc/queries/user.sql` | Adicionar `telefone` nas queries CreateUser e UpdateUser |\n\n### 5.3 Arquivos de Referencia\n| Arquivo | Motivo da Consulta |\n|---------|-------------------|\n| `internal/db/sqlc/sqlc.yaml` | Verificar configuracao SQLC (engine, paths) |\n| `internal/db/migrations/003_add_telefone_usuarios.sql` | Verificar tipo da coluna (TEXT nullable) |\n\n---\n\n## 6. Testes\n\n### 6.1 Testes Unitarios\n- N/A \u00e2\u20ac\u201d queries SQL e codigo gerado SQLC nao possuem logica testavel unitariamente\n\n### 6.2 Testes de Integracao\n- [ ] Teste: `CreateUser` com campo `telefone` preenchido retorna valor correto \u00e2\u20ac\u201d sera coberto na task T3/T4 (repository)\n- [ ] Teste: `CreateUser` com campo `telefone` NULL funciona corretamente \u00e2\u20ac\u201d sera coberto na task T3/T4\n- [ ] Teste: `UpdateUser` com campo `telefone` atualiza valor corretamente \u00e2\u20ac\u201d sera coberto na task T3/T4\n\n### 6.3 Testes E2E\n- N/A\n\n### 6.4 Cenarios de Erro\n- [ ] Cenario: `make sqlc` gera codigo sem erros de compilacao (validar com `make build`)\n\n---\n\n## 7. Notas / Observacoes\n- O tipo do campo `Telefone` no codigo gerado sera `sql.NullString` pois a coluna e nullable\n- As queries SELECT usam `SELECT *` e portanto capturarao a coluna automaticamente\n- Executar `make sqlc` e obrigatorio apos editar queries \u00e2\u20ac\u201d o codigo gerado em `internal/db/sqlc/` sera atualizado automaticamente\n\n---\n\n## 8. Checklist Final\n- [ ] Implementada conforme SPEC\n- [ ] Codigo SQLC regenerado com sucesso\n- [ ] Aceite tecnico atendido\n- [ ] Revisada\n- [ ] Integrada a branch principal\n\n---\n---\n\n# T3.md \u00e2\u20ac\u201d Adicionar campo Phone ao repository e atualizar mapeamento\n\n## 1. Identificacao\n- **ID**: T3\n- **Nome da Task**: Adicionar campo Phone ao modelo User do repository e atualizar mapeamento\n- **Responsavel**: \u00e2\u20ac\u201d\n- **Status**: A Fazer\n- **Fase**: Fase 2 \u00e2\u20ac\u201d Camadas de Negocio\n- **Dependencias**: T2\n- **User Stories Relacionadas**: US-10\n\n---\n\n## 2. Objetivo da Task\nAdicionar o campo `Phone` (opcional, ponteiro `*string`) ao modelo `User` do repository, e atualizar as funcoes `Create`, `Update` e `toUser` para mapear `telefone` (banco) para `Phone` (Go).\n\n---\n\n## 3. Descricao Detalhada\n\nEditar `internal/repository/user_repository.go`:\n\n**1. Adicionar campo na struct `User`:**\n```go\ntype User struct {\n ID string\n Name string\n Email string\n PasswordHash string\n Address string\n Phone *string // novo campo - ponteiro pois e opcional (nullable no banco)\n CreatedAt time.Time\n UpdatedAt time.Time\n}\n```\n\n**2. Atualizar funcao `Create`** \u00e2\u20ac\u201d mapear `Phone` para `Telefone` nos params SQLC:\n```go\nparams := sqlc.CreateUserParams{\n // ... campos existentes ...\n Telefone: toNullString(user.Phone),\n // ...\n}\n```\n\n**3. Atualizar funcao `Update`** \u00e2\u20ac\u201d mapear `Phone` para `Telefone` nos params SQLC:\n```go\nparams := sqlc.UpdateUserParams{\n // ... campos existentes ...\n Telefone: toNullString(user.Phone),\n // ...\n}\n```\n\n**4. Atualizar funcao `toUser`** \u00e2\u20ac\u201d mapear `Telefone` (sqlc) para `Phone` (dominio):\n```go\nfunc toUser(u sqlc.Usuario) *User {\n return &User{\n // ... campos existentes ...\n Phone: fromNullString(u.Telefone),\n // ...\n }\n}\n```\n\n**5. Criar funcoes helper** para conversao `*string` <-> `sql.NullString`:\n```go\nfunc toNullString(s *string) sql.NullString {\n if s == nil {\n return sql.NullString{}\n }\n return sql.NullString{String: *s, Valid: true}\n}\n\nfunc fromNullString(ns sql.NullString) *string {\n if !ns.Valid {\n return nil\n }\n return &ns.String\n}\n```\n\n**Decisoes tecnicas:**\n- `Phone` e `*string` (ponteiro) no modelo de dominio para representar campo nullable\n- Funcoes helper `toNullString`/`fromNullString` para converter entre `*string` e `sql.NullString`\n- Mapeamento: `telefone` (coluna banco, portugues) -> `Phone` (struct Go, ingles)\n\n---\n\n## 4. Aceite Tecnico (criterios objetivos)\n- [ ] Struct `User` possui campo `Phone *string`\n- [ ] Funcao `Create` mapeia `Phone` para `Telefone` no SQLC params\n- [ ] Funcao `Update` mapeia `Phone` para `Telefone` no SQLC params\n- [ ] Funcao `toUser` mapeia `Telefone` (sqlc) para `Phone` (dominio)\n- [ ] Funcoes helper `toNullString`/`fromNullString` implementadas\n- [ ] Codigo compila sem erros (`make build`)\n- [ ] Fluxos existentes (sem telefone) continuam funcionando \u00e2\u20ac\u201d campo e opcional\n\n---\n\n## 5. Arquivos Impactados\n\n### 5.1 Arquivos a Criar\n| Arquivo | Descricao |\n|---------|-----------|\n| Nenhum | \u00e2\u20ac\u201d |\n\n### 5.2 Arquivos a Modificar\n| Arquivo | Modificacao |\n|---------|------------|\n| `internal/repository/user_repository.go` | Adicionar campo Phone a struct User, atualizar Create, Update, toUser, adicionar helpers |\n\n### 5.3 Arquivos de Referencia\n| Arquivo | Motivo da Consulta |\n|---------|-------------------|\n| `internal/db/sqlc/` (codigo gerado) | Verificar nomes dos campos gerados (Telefone, sql.NullString) |\n\n---\n\n## 6. Testes\n\n### 6.1 Testes Unitarios\n- N/A \u00e2\u20ac\u201d repository nao possui logica testavel unitariamente (depende de banco)\n\n### 6.2 Testes de Integracao\nAtualizar `internal/repository/user_repository_integration_test.go`:\n\n- [ ] Teste: `TestUserRepository_Create_WithPhone` \u00e2\u20ac\u201d criar usuario com telefone e verificar que o valor e persistido e retornado\n- [ ] Teste: `TestUserRepository_Create_WithoutPhone` \u00e2\u20ac\u201d criar usuario sem telefone (nil) e verificar que `Phone` retorna nil\n- [ ] Teste: `TestUserRepository_Update_Phone` \u00e2\u20ac\u201d atualizar telefone de usuario existente e verificar\n- [ ] Teste: `TestUserRepository_Update_PhoneToNil` \u00e2\u20ac\u201d limpar telefone (setar nil) e verificar\n- [ ] Teste: atualizar `createTestUser` para incluir campo `Phone` (pode ser nil por padrao)\n- [ ] Teste: verificar que testes existentes continuam passando (campo `Phone` nil por padrao)\n\n### 6.3 Testes E2E\n- N/A\n\n### 6.4 Cenarios de Erro\n- [ ] Cenario: criar usuario com `Phone` nil nao gera erro\n- [ ] Cenario: update com `Phone` nil nao sobrescreve valor existente indevidamente (depende da logica do service, mas repository deve aceitar nil)\n\n---\n\n## 7. Notas / Observacoes\n- O campo `Telefone` no codigo gerado pelo SQLC sera `sql.NullString` porque a coluna e nullable no banco\n- Os helpers `toNullString`/`fromNullString` podem ser reutilizados por outros campos opcionais no futuro\n- O helper `createTestUser` nos testes de integracao precisa ser atualizado para incluir `Phone` (pode ser `nil` para manter compatibilidade)\n\n---\n\n## 8. Checklist Final\n- [ ] Implementada conforme SPEC\n- [ ] Testes de integracao criados/atualizados\n- [ ] Aceite tecnico atendido\n- [ ] Revisada\n- [ ] Integrada a branch principal\n\n---\n---\n\n# T4.md \u00e2\u20ac\u201d Adicionar campo Phone ao service com validacao\n\n## 1. Identificacao\n- **ID**: T4\n- **Nome da Task**: Adicionar campo Phone ao service com validacao\n- **Responsavel**: \u00e2\u20ac\u201d\n- **Status**: A Fazer\n- **Fase**: Fase 2 \u00e2\u20ac\u201d Camadas de Negocio\n- **Dependencias**: T3\n- **User Stories Relacionadas**: US-10\n\n---\n\n## 2. Objetivo da Task\nAdicionar o campo `Phone` (opcional) nos inputs `CreateUserInput` e `UpdateUserInput` do service, implementar validacao de tamanho (10 a 15 caracteres) e integrar com o repository.\n\n---\n\n## 3. Descricao Detalhada\n\nEditar `internal/service/user_service.go`:\n\n**1. Adicionar erro de validacao:**\n```go\nvar (\n // ... erros existentes ...\n ErrInvalidPhone = errors.New(\"telefone deve ter entre 10 e 15 caracteres\")\n)\n```\n\n**2. Adicionar campo em `CreateUserInput`:**\n```go\ntype CreateUserInput struct {\n Name string\n Email string\n Password string\n Address string\n Phone *string // opcional\n}\n```\n\n**3. Adicionar campo em `UpdateUserInput`:**\n```go\ntype UpdateUserInput struct {\n Name *string\n Email *string\n Address *string\n Phone *string // opcional\n NewPassword *string\n CurrentPassword *string\n}\n```\n\n**4. Adicionar validacao em `CreateUser`:**\nApos as validacoes existentes, antes de criar o usuario:\n```go\nif input.Phone != nil {\n if len(*input.Phone) < 10 || len(*input.Phone) > 15 {\n return nil, ErrInvalidPhone\n }\n}\n```\n\nE mapear para o repository:\n```go\nuser := &repository.User{\n // ... campos existentes ...\n Phone: input.Phone,\n // ...\n}\n```\n\n**5. Adicionar validacao e mapeamento em `UpdateUser`:**\nNa secao de campos opcionais:\n```go\nif input.Phone != nil {\n if len(*input.Phone) < 10 || len(*input.Phone) > 15 {\n return nil, ErrInvalidPhone\n }\n currentUser.Phone = input.Phone\n}\n```\n\nE atualizar a verificacao de \"nenhum campo\":\n```go\nif input.Name == nil && input.Email == nil && input.Address == nil && input.Phone == nil && input.NewPassword == nil {\n return nil, ErrNoFieldsToUpdate\n}\n```\n\n---\n\n## 4. Aceite Tecnico (criterios objetivos)\n- [ ] `CreateUserInput` possui campo `Phone *string`\n- [ ] `UpdateUserInput` possui campo `Phone *string`\n- [ ] Erro `ErrInvalidPhone` exportado com mensagem em portugues\n- [ ] Validacao: se `Phone` informado, deve ter entre 10 e 15 caracteres\n- [ ] Validacao: `Phone` nil e aceito sem erro (campo opcional)\n- [ ] `CreateUser` mapeia `Phone` para `repository.User`\n- [ ] `UpdateUser` mapeia `Phone` para `currentUser` quando informado\n- [ ] `UpdateUser` aceita `Phone` como campo unico para update (verificacao de \"nenhum campo\")\n- [ ] Codigo compila sem erros\n\n---\n\n## 5. Arquivos Impactados\n\n### 5.1 Arquivos a Criar\n| Arquivo | Descricao |\n|---------|-----------|\n| Nenhum | \u00e2\u20ac\u201d |\n\n### 5.2 Arquivos a Modificar\n| Arquivo | Modificacao |\n|---------|------------|\n| `internal/service/user_service.go` | Adicionar ErrInvalidPhone, campo Phone nos inputs, validacao e mapeamento |\n\n### 5.3 Arquivos de Referencia\n| Arquivo | Motivo da Consulta |\n|---------|-------------------|\n| `internal/repository/user_repository.go` | Verificar struct User com campo Phone |\n\n---\n\n## 6. Testes\n\n### 6.1 Testes Unitarios\nAtualizar `internal/service/user_service_test.go`:\n\n**Testes novos:**\n- [ ] Teste: `TestCreateUser_WithPhone_Success` \u00e2\u20ac\u201d criar usuario com telefone valido (ex: \"11987654321\", 11 chars)\n- [ ] Teste: `TestCreateUser_WithoutPhone_Success` \u00e2\u20ac\u201d criar usuario sem telefone (nil) \u00e2\u20ac\u201d validar que nao gera erro\n- [ ] Teste: `TestCreateUser_PhoneTooShort` \u00e2\u20ac\u201d telefone com 9 caracteres retorna `ErrInvalidPhone`\n- [ ] Teste: `TestCreateUser_PhoneTooLong` \u00e2\u20ac\u201d telefone com 16 caracteres retorna `ErrInvalidPhone`\n- [ ] Teste: `TestCreateUser_PhoneExactly10Chars` \u00e2\u20ac\u201d limite inferior aceito\n- [ ] Teste: `TestCreateUser_PhoneExactly15Chars` \u00e2\u20ac\u201d limite superior aceito\n- [ ] Teste: `TestUpdateUser_PhoneOnly` \u00e2\u20ac\u201d atualizar apenas telefone com sucesso\n- [ ] Teste: `TestUpdateUser_PhoneInvalid` \u00e2\u20ac\u201d telefone invalido retorna `ErrInvalidPhone`\n- [ ] Teste: `TestUpdateUser_PhoneWithOtherFields` \u00e2\u20ac\u201d atualizar telefone junto com outros campos\n\n**Testes existentes a atualizar:**\n- [ ] Atualizar `MockUserRepository` se necessario (interface nao muda, mas `User` struct ganha campo)\n- [ ] Atualizar `returnUser` em testes existentes para incluir `Phone` (pode ser nil)\n- [ ] Garantir que testes existentes continuam passando\n\n### 6.2 Testes de Integracao\n- N/A \u00e2\u20ac\u201d coberto pelos testes unitarios com mock\n\n### 6.3 Testes E2E\n- N/A\n\n### 6.4 Cenarios de Erro\n- [ ] Cenario: telefone com 9 caracteres -> `ErrInvalidPhone`\n- [ ] Cenario: telefone com 16 caracteres -> `ErrInvalidPhone`\n- [ ] Cenario: telefone vazio string (\"\") -> `ErrInvalidPhone` (len < 10)\n- [ ] Cenario: telefone nil -> aceito sem erro\n\n---\n\n## 7. Notas / Observacoes\n- A validacao de telefone e simples: apenas tamanho entre 10 e 15 chars, conforme SPEC\n- Nao ha validacao de formato (apenas digitos, por exemplo) \u00e2\u20ac\u201d se necessario, criar US separada\n- O campo `Phone` no `UpdateUserInput` participa da verificacao de \"nenhum campo para atualizar\"\n\n---\n\n## 8. Checklist Final\n- [ ] Implementada conforme SPEC\n- [ ] Testes unitarios criados/atualizados\n- [ ] Aceite tecnico atendido\n- [ ] Revisada\n- [ ] Integrada a branch principal\n\n---\n---\n\n# T5.md \u00e2\u20ac\u201d Atualizar proto e handler gRPC para incluir campo phone\n\n## 1. Identificacao\n- **ID**: T5\n- **Nome da Task**: Atualizar proto e handler gRPC para incluir campo phone\n- **Responsavel**: \u00e2\u20ac\u201d\n- **Status**: A Fazer\n- **Fase**: Fase 3 \u00e2\u20ac\u201d Interface gRPC\n- **Dependencias**: T4\n- **User Stories Relacionadas**: US-10\n\n---\n\n## 2. Objetivo da Task\nAdicionar campo `phone` nas mensagens proto (User, CreateUserRequest, UpdateUserRequest), regenerar codigo, e atualizar o handler gRPC para mapear o campo.\n\n---\n\n## 3. Descricao Detalhada\n\n### 3.1 Atualizar Proto\n\nEditar `api/proto/v1/user.proto`:\n\n**Mensagem `User`** \u00e2\u20ac\u201d adicionar campo `phone` com field number 7:\n```proto\nmessage User {\n string id = 1;\n string name = 2;\n string email = 3;\n string address = 4;\n string created_at = 5;\n string updated_at = 6;\n optional string phone = 7;\n}\n```\n\n**Mensagem `CreateUserRequest`** \u00e2\u20ac\u201d adicionar campo `phone` com field number 5:\n```proto\nmessage CreateUserRequest {\n string name = 1;\n string email = 2;\n string password = 3;\n string address = 4;\n optional string phone = 5;\n}\n```\n\n**Mensagem `UpdateUserRequest`** \u00e2\u20ac\u201d adicionar campo `phone` com field number 6:\n```proto\nmessage UpdateUserRequest {\n optional string name = 1;\n optional string email = 2;\n optional string address = 3;\n optional string new_password = 4;\n optional string current_password = 5;\n optional string phone = 6;\n}\n```\n\nExecutar `make proto` (ou `buf generate`) para regenerar codigo em `gen/`.\n\n### 3.2 Atualizar Handler\n\nEditar `internal/handler/grpc/user_handler.go`:\n\n**Funcao `CreateUser`** \u00e2\u20ac\u201d mapear `Phone` do request para input:\n```go\ninput := service.CreateUserInput{\n Name: req.GetName(),\n Email: req.GetEmail(),\n Password: req.GetPassword(),\n Address: req.GetAddress(),\n Phone: req.Phone, // optional -> *string\n}\n```\n\n**Funcao `UpdateUser`** \u00e2\u20ac\u201d adicionar mapeamento de phone:\n```go\nif req.Phone != nil {\n input.Phone = req.Phone\n}\n```\n\n**Funcao `domainUserToProto`** \u00e2\u20ac\u201d mapear `Phone` para proto:\n```go\nfunc domainUserToProto(user *repository.User) *pb.User {\n return &pb.User{\n // ... campos existentes ...\n Phone: user.Phone,\n }\n}\n```\n\n**Funcao `mapServiceError`** \u00e2\u20ac\u201d adicionar mapeamento de `ErrInvalidPhone`:\n```go\ncase errors.Is(err, service.ErrInvalidPhone):\n return status.Errorf(codes.InvalidArgument, \"%v\", err)\n```\nAdicionar na clausula `switch` junto com os outros erros de `InvalidArgument`.\n\n---\n\n## 4. Aceite Tecnico (criterios objetivos)\n- [ ] Proto `user.proto` inclui campo `phone` em User (field 7), CreateUserRequest (field 5), UpdateUserRequest (field 6)\n- [ ] `make proto` executa sem erros\n- [ ] Handler `CreateUser` mapeia `Phone` do request para service input\n- [ ] Handler `UpdateUser` mapeia `Phone` do request para service input\n- [ ] Funcao `domainUserToProto` mapeia `Phone` do dominio para proto\n- [ ] `mapServiceError` trata `ErrInvalidPhone` como `InvalidArgument`\n- [ ] Codigo compila sem erros (`make build`)\n- [ ] Endpoint `CreateUser` aceita e persiste telefone\n- [ ] Endpoint `UpdateUser` aceita e atualiza telefone\n- [ ] Endpoint `GetUserLogged` retorna telefone\n\n---\n\n## 5. Arquivos Impactados\n\n### 5.1 Arquivos a Criar\n| Arquivo | Descricao |\n|---------|-----------|\n| Nenhum | \u00e2\u20ac\u201d |\n\n### 5.2 Arquivos a Modificar\n| Arquivo | Modificacao |\n|---------|------------|\n| `api/proto/v1/user.proto` | Adicionar campo `phone` em User, CreateUserRequest, UpdateUserRequest |\n| `internal/handler/grpc/user_handler.go` | Mapear campo Phone em CreateUser, UpdateUser, domainUserToProto, mapServiceError |\n\n### 5.3 Arquivos de Referencia\n| Arquivo | Motivo da Consulta |\n|---------|-------------------|\n| `gen/proto/v1/` (codigo gerado) | Verificar nomes de campos gerados apos `make proto` |\n| `internal/service/user_service.go` | Verificar nomes dos campos nos inputs e erro ErrInvalidPhone |\n\n---\n\n## 6. Testes\n\n### 6.1 Testes Unitarios\nAtualizar `internal/handler/grpc/user_handler_test.go`:\n\n**Testes novos:**\n- [ ] Teste: `TestUserHandler_CreateUser_WithPhone` \u00e2\u20ac\u201d enviar CreateUserRequest com phone e verificar que o input service recebe o valor\n- [ ] Teste: `TestUserHandler_CreateUser_WithoutPhone` \u00e2\u20ac\u201d enviar sem phone e verificar que funciona normalmente\n- [ ] Teste: `TestUserHandler_CreateUser_InvalidPhone` \u00e2\u20ac\u201d service retorna `ErrInvalidPhone`, verificar que handler retorna `InvalidArgument`\n- [ ] Teste: `TestUserHandler_UpdateUser_PhoneOnly` \u00e2\u20ac\u201d atualizar apenas telefone via gRPC\n- [ ] Teste: `TestUserHandler_UpdateUser_InvalidPhone` \u00e2\u20ac\u201d telefone invalido retorna `InvalidArgument`\n- [ ] Teste: `TestUserHandler_GetUserLogged_ReturnsPhone` \u00e2\u20ac\u201d verificar que GetUserLogged retorna o campo phone no proto\n- [ ] Teste: atualizar `TestDomainUserToProto` para verificar campo `Phone`\n- [ ] Teste: atualizar `TestMapServiceError_AllErrors` para incluir `ErrInvalidPhone`\n\n**Testes existentes a atualizar:**\n- [ ] Atualizar `MockUserService` se a interface mudou (adicionar `Phone` nos inputs)\n- [ ] Atualizar testes existentes de CreateUser para incluir `Phone` no input (pode ser nil)\n- [ ] Atualizar testes existentes de UpdateUser para incluir `Phone` no input (pode ser nil)\n- [ ] Garantir que todos os testes existentes continuam passando\n\n### 6.2 Testes de Integracao\n- N/A\n\n### 6.3 Testes E2E\n- N/A \u00e2\u20ac\u201d testes do handler com mock cobrem a integracao proto<->service\n\n### 6.4 Cenarios de Erro\n- [ ] Cenario: `ErrInvalidPhone` -> gRPC `InvalidArgument`\n- [ ] Cenario: phone nil no request -> nao gera erro\n- [ ] Cenario: phone vazio no UpdateUser -> nao inclui no input (req.Phone seria nil)\n\n---\n\n## 7. Notas / Observacoes\n- O campo `optional string phone` no proto gera `*string` no Go, o que e compativel com o tipo `*string` no service e repository\n- Os field numbers escolhidos (7 para User, 5 para CreateUserRequest, 6 para UpdateUserRequest) sao os proximos disponiveis em cada mensagem\n- `make proto` deve ser executado ANTES de compilar o handler, pois o handler depende do codigo gerado\n- A mensagem `CreateUserResponse` e `LoginResponse` NAO incluem phone (conforme estrutura atual do proto \u00e2\u20ac\u201d elas retornam campos especificos, nao a mensagem User)\n\n---\n\n## 8. Checklist Final\n- [ ] Implementada conforme SPEC\n- [ ] Testes unitarios criados/atualizados\n- [ ] Aceite tecnico atendido\n- [ ] Revisada\n- [ ] Integrada a branch principal\n\n---\n---\n\n# T6.md \u00e2\u20ac\u201d Validacao cruzada e testes end-to-end da feature completa\n\n## 1. Identificacao\n- **ID**: T6\n- **Nome da Task**: Validacao cruzada e testes end-to-end da feature completa\n- **Responsavel**: \u00e2\u20ac\u201d\n- **Status**: A Fazer\n- **Fase**: Fase 4 \u00e2\u20ac\u201d Testes e Validacao\n- **Dependencias**: T5\n- **User Stories Relacionadas**: US-10\n\n---\n\n## 2. Objetivo da Task\nGarantir que toda a stack funciona de ponta a ponta: migracao aplica, queries funcionam, repository persiste e recupera, service valida, handler expoe via gRPC. Executar todos os testes existentes e validar que nenhum fluxo existente foi quebrado.\n\n---\n\n## 3. Descricao Detalhada\n\n**1. Executar suite completa de testes:**\n```bash\nmake test\n```\nVerificar que TODOS os testes passam \u00e2\u20ac\u201d tanto os novos quanto os existentes.\n\n**2. Executar build completo:**\n```bash\nmake build\n```\nVerificar que nao ha erros de compilacao.\n\n**3. Validar cenarios manualmente (se aplicavel):**\n- Subir servidor com `make run`\n- Testar via grpcurl ou cliente gRPC:\n - CreateUser com telefone\n - CreateUser sem telefone\n - UpdateUser adicionando telefone\n - UpdateUser removendo telefone (setar vazio / nil)\n - GetUserLogged retornando telefone\n - CreateUser com telefone invalido (< 10 chars)\n\n**4. Verificar que testes de regressao passam:**\n- Testes de service (unitarios com mock)\n- Testes de handler (unitarios com mock)\n- Testes de repository (integracao com SQLite)\n\n---\n\n## 4. Aceite Tecnico (criterios objetivos)\n- [ ] `make test` passa sem falhas\n- [ ] `make build` compila sem erros\n- [ ] Nenhum teste existente quebrado\n- [ ] Novos testes (de T3, T4, T5) incluidos e passando\n- [ ] Campo `phone` flui corretamente de gRPC request -> handler -> service -> repository -> banco -> retorno\n\n---\n\n## 5. Arquivos Impactados\n\n### 5.1 Arquivos a Criar\n| Arquivo | Descricao |\n|---------|-----------|\n| Nenhum | \u00e2\u20ac\u201d |\n\n### 5.2 Arquivos a Modificar\n| Arquivo | Modificacao |\n|---------|------------|\n| Nenhum | Task de validacao \u00e2\u20ac\u201d apenas execucao de testes |\n\n### 5.3 Arquivos de Referencia\n| Arquivo | Motivo da Consulta |\n|---------|-------------------|\n| Todos os arquivos de teste | Verificar que passam |\n| `Makefile` | Verificar comandos make test, make build |\n\n---\n\n## 6. Testes\n\n### 6.1 Testes Unitarios\n- [ ] Executar `CGO_ENABLED=1 go test ./internal/service/... -v` \u00e2\u20ac\u201d todos devem passar\n- [ ] Executar `CGO_ENABLED=1 go test ./internal/handler/grpc/... -v` \u00e2\u20ac\u201d todos devem passar\n\n### 6.2 Testes de Integracao\n- [ ] Executar `CGO_ENABLED=1 go test ./internal/repository/... -v` \u00e2\u20ac\u201d todos devem passar\n\n### 6.3 Testes E2E\n- [ ] (Opcional) Teste manual via grpcurl: CreateUser com phone, GetUserLogged retorna phone\n\n### 6.4 Cenarios de Erro\n- [ ] Cenario: nenhum teste existente quebrado (regressao zero)\n\n---\n\n## 7. Notas / Observacoes\n- Esta task e de validacao final \u00e2\u20ac\u201d nao cria codigo novo\n- Se algum teste falhar, a correcao deve ser feita na task correspondente (T1-T5)\n- O objetivo e garantir integridade antes de considerar a feature completa\n\n---\n\n## 8. Checklist Final\n- [ ] `make test` sem falhas\n- [ ] `make build` sem erros\n- [ ] Aceite tecnico atendido\n- [ ] Revisada\n- [ ] Integrada a branch principal\n\n---\n---\n\n# task_plan.md \u00e2\u20ac\u201d Documento de Referencia\n\n# TASK PLAN -- Adicionar Telefone ao Modulo de Usuario\n\n## 1. Identificacao\n- **Feature/Projeto**: Adicionar Telefone ao Modulo de Usuario\n- **Responsavel (Tech Lead)**: \u00e2\u20ac\u201d\n- **Data**: 2026-03-08\n- **Status**: Rascunho\n- **SPEC Relacionado**: docs/feature-telefone-usuario/v1/spec_tech.md (referencia inline no SPEC_TECH fornecido)\n- **PRD Relacionado**: docs/feature-telefone-usuario/v1/prd.md\n\n---\n\n## 2. Objetivo do Task Plan\nAdicionar o campo `telefone` (opcional) ao modulo de usuario em todas as camadas da aplicacao: banco de dados, queries SQLC, repository, service (com validacao de 10-15 caracteres), proto e handler gRPC. Ao final, o campo telefone estara disponivel nos endpoints CreateUser, UpdateUser e GetUserLogged.\n\n---\n\n## 3. Macro-Fases (alto nivel)\n- **Fase 1 -- Banco de Dados e Queries**\n - Objetivo: Criar a infraestrutura de dados para suportar o campo telefone\n - Tasks: T1, T2\n- **Fase 2 -- Camadas de Negocio (Repository + Service)**\n - Objetivo: Integrar o campo telefone nas camadas de dominio com mapeamento e validacao\n - Tasks: T3, T4\n- **Fase 3 -- Interface gRPC (Proto + Handler)**\n - Objetivo: Expor o campo telefone via API gRPC\n - Tasks: T5\n- **Fase 4 -- Testes e Validacao**\n - Objetivo: Garantir integridade da feature e regressao zero\n - Tasks: T6\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) | Fase 1 | Nenhuma | Sim (inicio) | A Fazer |\n| T2 | Atualizar queries SQLC para incluir coluna telefone | [T2](tasks/T2.md) | Fase 1 | T1 | Nao | A Fazer |\n| T3 | Adicionar campo Phone ao repository e atualizar mapeamento | [T3](tasks/T3.md) | Fase 2 | T2 | Nao | A Fazer |\n| T4 | Adicionar campo Phone ao service com validacao | [T4](tasks/T4.md) | Fase 2 | T3 | Nao | A Fazer |\n| T5 | Atualizar proto e handler gRPC para incluir campo phone | [T5](tasks/T5.md) | Fase 3 | T4 | Nao | A Fazer |\n| T6 | Validacao cruzada e testes end-to-end da feature completa | [T6](tasks/T6.md) | Fase 4 | 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: Informar telefone para receber SMS | 3.1 Banco (migracao), 3.2 SQLC (queries), 3.3 Repository (campo Phone), 3.4 Service (validacao), 3.5 Handler (mapeamento), 3.6 Proto (campo phone) | T1, T2, T3, T4, T5, T6 | A Fazer |\n\n> Todas as user stories do SPEC_TECH foram decompostas em tasks.\n\n---\n\n## 6. Dependencias Gerais\n\n- **Cadeia sequencial**: T1 -> T2 -> T3 -> T4 -> T5 -> T6\n- As tasks sao sequenciais pois cada camada depende da anterior (banco -> SQLC -> repository -> service -> handler)\n- **Ferramentas externas necessarias**: `buf` (proto), `sqlc` (queries) \u00e2\u20ac\u201d devem estar instaladas\n- **Prerequisito**: `CGO_ENABLED=1` para compilacao (dependencia go-sqlite3)\n- Nao ha dependencia de tasks de outras features\n- Nao ha paralelismo possivel nesta feature (cadeia linear de dependencias por camada)\n\n---\n\n## 7. Criterios de Conclusao da Feature\nA feature sera considerada concluida quando:\n- [x] Todas as tasks estiverem completas (T1-T6)\n- [ ] Testes validados (`make test` sem falhas)\n- [ ] Criterios tecnicos do SPEC atendidos (campo telefone opcional, validacao 10-15 chars)\n- [ ] Nenhum comportamento divergente do PRD\n- [ ] Todas as User Stories cobertas (US-10)\n- [ ] Build aprovado (`make build` sem erros)\n\n---\n\n## 8. Riscos & Mitigacoes\n- **Risco**: Migracao SQLite com ALTER TABLE pode ter limitacoes -> **Mitigacao**: ALTER TABLE ADD COLUMN e suportado sem restricoes no SQLite\n- **Risco**: Campo `telefone` nullable gera `sql.NullString` no SQLC, adicionando complexidade -> **Mitigacao**: Funcoes helper `toNullString`/`fromNullString` encapsulam a conversao\n- **Risco**: Testes existentes quebram por mudanca na struct User -> **Mitigacao**: Campo `Phone` e ponteiro (`*string`), valor zero e nil, testes existentes nao precisam setar\n\n---\n\n## 9. Checklist Final\n- [x] Task Plan completo\n- [x] Tasks mapeadas (T1-T6)\n- [x] Dependencias validadas (cadeia sequencial)\n- [x] Rastreabilidade User Stories -> Tasks preenchida (US-10 -> T1-T6)\n- [x] Pronto para execucao\n\n---\n\nArquivos que seriam salvos:\n- `docs/telefone-usuario/v1/task_plan.md`\n- `docs/telefone-usuario/v1/tasks/T1.md`\n- `docs/telefone-usuario/v1/tasks/T2.md`\n- `docs/telefone-usuario/v1/tasks/T3.md`\n- `docs/telefone-usuario/v1/tasks/T4.md`\n- `docs/telefone-usuario/v1/tasks/T5.md`\n- `docs/telefone-usuario/v1/tasks/T6.md`\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 6 tasks (T1 migracao, T2 SQLC, T3 repository, T4 service, T5 proto+handler, T6 validacao). Proporcional a complexidade."}, {"text": "Respeita a ordem de dependencias das camadas (migracao > SQLC > repository > service > handler > proto)", "passed": true, "evidence": "Ordem: T1(migracao) -> T2(SQLC) -> T3(repository) -> T4(service) -> T5(proto+handler) -> T6(validacao). Dependencias respeitadas."}, {"text": "Mapeia a US-10 na tabela de rastreabilidade do task_plan", "passed": true, "evidence": "Secao 5 do task_plan.md: 'US-10: Informar telefone para receber SMS' mapeada para T1-T6. Cada task individual tambem referencia 'User Stories Relacionadas: US-10'."}, {"text": "Cada task segue o template completo (secoes 1-8) ou marca secoes N/A com justificativa", "passed": true, "evidence": "Todas as 6 tasks possuem secoes: 1.Identificacao, 2.Objetivo, 3.Descricao Detalhada, 4.Aceite Tecnico, 5.Arquivos Impactados, 6.Testes, 7.Notas, 8.Checklist Final."}, {"text": "Salva task_plan.md como documento de REFERENCIA (sem corpo detalhado de tasks dentro dele)", "passed": true, "evidence": "task_plan.md aparece como documento separado ao final com secoes: Identificacao, Objetivo, Macro-Fases, Lista de Tasks (links para TN.md), Rastreabilidade, Dependencias, Criterios, Riscos, Checklist."}]}}, {"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-08 |\n| **SPEC_TECH** | docs/feature-telefone-usuario/v1/spec_tech.md |\n| **Total de Tasks** | 7 |\n| **Estimativa Total** | ~2h |\n\n## 2. Resumo\n\nAdicionar campo opcional `telefone` a tabela `usuarios` e expor via gRPC em CreateUser, UpdateUser, GetUserLogged e na mensagem User. Inclui migracao de banco, atualizacao de queries SQLC, repository, service (com validacao), handler gRPC e proto.\n\n---\n\n## 3. Tasks\n\n### TASK-1: Criar migracao de banco de dados\n\n**Camada:** Database\n**Arquivo a criar:**\n- `internal/db/migrations/003_add_telefone_usuarios.sql`\n\n**Descricao:**\nCriar nova migracao para adicionar a coluna `telefone` (TEXT, opcional/nullable) a tabela `usuarios`.\n\n**Conteudo:**\n```sql\nALTER TABLE usuarios ADD COLUMN telefone TEXT;\n```\n\n**Observacoes:**\n- A coluna e opcional (sem NOT NULL), conforme SPEC_TECH\n- Seguir o padrao de numeracao sequencial (proxima e 003)\n- NAO editar migracoes existentes (001, 002)\n- Sem migracao down pois o SQLite nao suporta DROP COLUMN de forma nativa em versoes antigas\n\n**Criterio de aceite:**\n- Arquivo `003_add_telefone_usuarios.sql` criado\n- Migracao executa sem erro na inicializacao da aplicacao\n- Coluna `telefone` existe na tabela `usuarios` apos boot\n\n**Dependencias:** Nenhuma\n\n---\n\n### TASK-2: Atualizar queries SQLC\n\n**Camada:** SQLC Queries\n**Arquivo a editar:**\n- `internal/db/sqlc/queries/user.sql`\n\n**Descricao:**\nAtualizar as queries `CreateUser` e `UpdateUser` para incluir a coluna `telefone`.\n\n**Alteracoes:**\n\nNa query `CreateUser`, adicionar `telefone` na lista de colunas e valores:\n```sql\n-- name: CreateUser :one\nINSERT INTO usuarios (id, nome, email, senha_hash, endereco, telefone, data_criacao, data_atualizacao)\nVALUES (?, ?, ?, ?, ?, ?, ?, ?)\nRETURNING *;\n```\n\nNa query `UpdateUser`, adicionar `telefone` no SET:\n```sql\n-- name: UpdateUser :one\nUPDATE usuarios\nSET nome = ?,\n email = ?,\n senha_hash = ?,\n endereco = ?,\n telefone = ?,\n data_atualizacao = ?\nWHERE id = ?\nRETURNING *;\n```\n\n**Observacoes:**\n- As queries `GetUserByID` e `GetUserByEmail` usam `SELECT *`, entao ja retornarao o campo `telefone` automaticamente\n- Apos editar, executar `make sqlc` para regenerar codigo em `internal/db/sqlc/`\n\n**Criterio de aceite:**\n- Queries atualizadas com coluna `telefone`\n- `make sqlc` executa sem erros\n- Codigo gerado em `internal/db/sqlc/` reflete o novo campo `Telefone`\n\n**Dependencias:** TASK-1\n\n---\n\n### TASK-3: Atualizar Repository\n\n**Camada:** Repository\n**Arquivo a editar:**\n- `internal/repository/user_repository.go`\n\n**Descricao:**\nAdicionar campo `Phone` ao modelo `User` e atualizar mapeamentos entre SQLC e dominio.\n\n**Alteracoes:**\n\n1. Adicionar campo `Phone` na struct `User`:\n```go\ntype User struct {\n ID string\n Name string\n Email string\n PasswordHash string\n Address string\n Phone string // novo campo\n CreatedAt time.Time\n UpdatedAt time.Time\n}\n```\n\n2. Atualizar metodo `Create` para mapear `Phone` -> `Telefone` nos params SQLC:\n```go\nparams := sqlc.CreateUserParams{\n ID: user.ID,\n Nome: user.Name,\n Email: user.Email,\n SenhaHash: user.PasswordHash,\n Endereco: user.Address,\n Telefone: sql.NullString{String: user.Phone, Valid: user.Phone != \"\"},\n DataCriacao: user.CreatedAt,\n DataAtualizacao: user.UpdatedAt,\n}\n```\n\n3. Atualizar metodo `Update` da mesma forma, adicionando `Telefone` nos params.\n\n4. Atualizar funcao `toUser` para mapear `Telefone` -> `Phone`:\n```go\nfunc toUser(u sqlc.Usuario) *User {\n return &User{\n ID: u.ID,\n Name: u.Nome,\n Email: u.Email,\n PasswordHash: u.SenhaHash,\n Address: u.Endereco,\n Phone: u.Telefone.String, // NullString -> string\n CreatedAt: u.DataCriacao,\n UpdatedAt: u.DataAtualizacao,\n }\n}\n```\n\n**Observacoes:**\n- O tipo SQLC gerado para `telefone` sera `sql.NullString` pois a coluna e nullable\n- Verificar o tipo exato apos `make sqlc` e ajustar o mapeamento se necessario\n\n**Criterio de aceite:**\n- Struct `User` possui campo `Phone string`\n- Create e Update mapeiam `Phone` corretamente para `Telefone` do SQLC\n- `toUser` mapeia `Telefone` de volta para `Phone`\n- Compilacao sem erros\n\n**Dependencias:** TASK-2\n\n---\n\n### TASK-4: Atualizar Service\n\n**Camada:** Service\n**Arquivo a editar:**\n- `internal/service/user_service.go`\n\n**Descricao:**\nAdicionar campo `Phone` nos inputs e incluir validacao de tamanho (10-15 caracteres quando informado).\n\n**Alteracoes:**\n\n1. Adicionar nova variavel de erro:\n```go\nErrInvalidPhone = errors.New(\"telefone deve ter entre 10 e 15 caracteres\")\n```\n\n2. Adicionar campo `Phone` em `CreateUserInput`:\n```go\ntype CreateUserInput struct {\n Name string\n Email string\n Password string\n Address string\n Phone string // novo, opcional\n}\n```\n\n3. Adicionar campo `Phone` em `UpdateUserInput`:\n```go\ntype UpdateUserInput struct {\n Name *string\n Email *string\n Address *string\n Phone *string // novo, opcional\n NewPassword *string\n CurrentPassword *string\n}\n```\n\n4. Em `CreateUser`, adicionar validacao antes de criar o usuario:\n```go\nif input.Phone != \"\" {\n if len(input.Phone) < 10 || len(input.Phone) > 15 {\n return nil, ErrInvalidPhone\n }\n}\n```\nE incluir `Phone: input.Phone` ao criar `repository.User`.\n\n5. Em `UpdateUser`:\n- Adicionar `input.Phone` na verificacao de \"nenhum campo\" (linha 171)\n- Adicionar bloco para processar phone:\n```go\nif input.Phone != nil {\n if *input.Phone != \"\" && (len(*input.Phone) < 10 || len(*input.Phone) > 15) {\n return nil, ErrInvalidPhone\n }\n currentUser.Phone = *input.Phone\n}\n```\n\n**Criterio de aceite:**\n- `CreateUserInput` e `UpdateUserInput` possuem campo Phone\n- Validacao de telefone (10-15 chars) funciona para CreateUser e UpdateUser\n- Telefone vazio e aceito (campo opcional)\n- `ErrInvalidPhone` definido e exportado\n\n**Dependencias:** TASK-3\n\n---\n\n### TASK-5: Atualizar Proto (user.proto)\n\n**Camada:** Proto/gRPC\n**Arquivo a editar:**\n- `api/proto/v1/user.proto`\n\n**Descricao:**\nAdicionar campo `phone` nas mensagens proto relevantes.\n\n**Alteracoes:**\n\n1. Mensagem `User` \u00e2\u20ac\u201d adicionar campo `phone` (field number 7):\n```proto\nmessage User {\n string id = 1;\n string name = 2;\n string email = 3;\n string address = 4;\n string created_at = 5;\n string updated_at = 6;\n string phone = 7;\n}\n```\n\n2. Mensagem `CreateUserRequest` \u00e2\u20ac\u201d adicionar campo `phone` (field number 5):\n```proto\nmessage CreateUserRequest {\n string name = 1;\n string email = 2;\n string password = 3;\n string address = 4;\n string phone = 5;\n}\n```\n\n3. Mensagem `UpdateUserRequest` \u00e2\u20ac\u201d adicionar campo `phone` (field number 6):\n```proto\nmessage UpdateUserRequest {\n optional string name = 1;\n optional string email = 2;\n optional string address = 3;\n optional string new_password = 4;\n optional string current_password = 5;\n optional string phone = 6;\n}\n```\n\n**Observacoes:**\n- Apos editar, executar `make proto` (ou `buf generate`) para regenerar codigo em `gen/`\n- NAO editar arquivos em `gen/` manualmente\n- `CreateUserResponse` e `LoginResponse` NAO precisam do campo phone (seguem o formato atual que retorna campos selecionados)\n\n**Criterio de aceite:**\n- `user.proto` atualizado com campo `phone` nas 3 mensagens\n- `make proto` executa sem erros\n- Codigo gerado em `gen/` reflete os novos campos\n\n**Dependencias:** Nenhuma (pode ser feita em paralelo com TASK-1 a TASK-4)\n\n---\n\n### TASK-6: Atualizar Handler gRPC\n\n**Camada:** Handler\n**Arquivo a editar:**\n- `internal/handler/grpc/user_handler.go`\n\n**Descricao:**\nAtualizar os handlers para passar o campo `phone` entre proto e service, e incluir na conversao dominio->proto.\n\n**Alteracoes:**\n\n1. Em `CreateUser`, adicionar `Phone` ao input:\n```go\nuser, err := h.userService.CreateUser(ctx, service.CreateUserInput{\n Name: req.GetName(),\n Email: req.GetEmail(),\n Password: req.GetPassword(),\n Address: req.GetAddress(),\n Phone: req.GetPhone(),\n})\n```\n\n2. Em `UpdateUser`, adicionar bloco para phone:\n```go\nif req.Phone != nil {\n input.Phone = req.Phone\n}\n```\n\n3. Em `domainUserToProto`, adicionar mapeamento de `Phone`:\n```go\nfunc domainUserToProto(user *repository.User) *pb.User {\n return &pb.User{\n Id: user.ID,\n Name: user.Name,\n Email: user.Email,\n Address: user.Address,\n Phone: user.Phone,\n CreatedAt: user.CreatedAt.Format(time.RFC3339),\n UpdatedAt: user.UpdatedAt.Format(time.RFC3339),\n }\n}\n```\n\n4. Em `mapServiceError`, adicionar mapeamento para `ErrInvalidPhone`:\n```go\ncase errors.Is(err, service.ErrInvalidPhone):\n return status.Errorf(codes.InvalidArgument, \"%v\", err)\n```\n\n**Criterio de aceite:**\n- CreateUser passa `Phone` do request para o service\n- UpdateUser passa `Phone` do request para o service\n- `domainUserToProto` inclui `Phone`\n- `mapServiceError` trata `ErrInvalidPhone`\n- Compilacao sem erros\n\n**Dependencias:** TASK-4, TASK-5\n\n---\n\n### TASK-7: Atualizar Testes\n\n**Camada:** Testes\n**Arquivos a editar:**\n- `internal/service/user_service_test.go`\n- `internal/handler/grpc/user_handler_test.go`\n\n**Descricao:**\nAdicionar testes para o novo campo `phone` e atualizar testes existentes que precisam refletir a presenca do campo.\n\n**Alteracoes em `user_service_test.go`:**\n\n1. Adicionar teste `TestCreateUser_WithPhone_Success` \u00e2\u20ac\u201d cria usuario com telefone valido\n2. Adicionar teste `TestCreateUser_PhoneTooShort` \u00e2\u20ac\u201d telefone com menos de 10 caracteres deve retornar `ErrInvalidPhone`\n3. Adicionar teste `TestCreateUser_PhoneTooLong` \u00e2\u20ac\u201d telefone com mais de 15 caracteres deve retornar `ErrInvalidPhone`\n4. Adicionar teste `TestCreateUser_PhoneEmpty_Success` \u00e2\u20ac\u201d telefone vazio e aceito (opcional)\n5. Adicionar teste `TestUpdateUser_PhoneOnly` \u00e2\u20ac\u201d atualiza somente telefone\n6. Adicionar teste `TestUpdateUser_PhoneInvalid` \u00e2\u20ac\u201d telefone invalido no update retorna `ErrInvalidPhone`\n7. Adicionar caso `\"telefone invalido\"` na table-driven `TestCreateUser_ValidationTableDriven`\n\n**Alteracoes em `user_handler_test.go`:**\n\n1. Adicionar teste `TestUserHandler_CreateUser_WithPhone` \u00e2\u20ac\u201d verifica passagem do phone do proto para o service\n2. Adicionar teste `TestUserHandler_UpdateUser_PhoneOnly` \u00e2\u20ac\u201d verifica atualizacao de phone\n3. Atualizar `TestDomainUserToProto` para incluir e validar campo `Phone`\n4. Adicionar `ErrInvalidPhone` ao `TestMapServiceError_AllErrors` com codigo `InvalidArgument`\n5. Atualizar testes existentes que criam `repository.User` para incluir campo `Phone` onde relevante (evitar warnings de campos faltantes)\n\n**Criterio de aceite:**\n- Todos os testes novos passam\n- Testes existentes continuam passando\n- Cobertura de: phone valido, phone vazio (opcional), phone curto demais, phone longo demais\n- `make test` passa sem erros\n\n**Dependencias:** TASK-4, TASK-6\n\n---\n\n## 4. Ordem de Execucao\n\n```\nTASK-1 (migracao) \u00e2\u201d\u20ac\u00e2\u201d\u20ac> TASK-2 (sqlc queries) \u00e2\u201d\u20ac\u00e2\u201d\u20ac> TASK-3 (repository) \u00e2\u201d\u20ac\u00e2\u201d\u20ac> TASK-4 (service) \u00e2\u201d\u20ac\u00e2\u201d\u20ac\u00e2\u201d\ufffd\n \u00e2\u201d\u0153\u00e2\u201d\u20ac\u00e2\u201d\u20ac> TASK-6 (handler) \u00e2\u201d\u20ac\u00e2\u201d\u20ac> TASK-7 (testes)\nTASK-5 (proto) \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\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\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\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\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\n```\n\n**Caminho critico:** TASK-1 -> TASK-2 -> TASK-3 -> TASK-4 -> TASK-6 -> TASK-7\n\n**Paralelismo possivel:** TASK-5 pode ser executada em paralelo com TASK-1 a TASK-4.\n\n---\n\n## 5. Comandos de Geracao/Verificacao\n\nApos as alteracoes, executar na seguinte ordem:\n\n```bash\n# 1. Gerar codigo proto (apos TASK-5)\nmake proto\n\n# 2. Gerar codigo SQLC (apos TASK-2)\nmake sqlc\n\n# 3. Compilar para verificar erros\nmake build\n\n# 4. Rodar todos os testes\nmake test\n\n# 5. Lint dos protobufs\nmake lint\n```\n\n---\n\n## 6. Arquivos Impactados (Resumo)\n\n| Arquivo | Acao |\n|---------|------|\n| `internal/db/migrations/003_add_telefone_usuarios.sql` | Criar |\n| `internal/db/sqlc/queries/user.sql` | Editar |\n| `internal/db/sqlc/` (gerado) | Regenerar via `make sqlc` |\n| `api/proto/v1/user.proto` | Editar |\n| `gen/proto/v1/` (gerado) | Regenerar via `make proto` |\n| `internal/repository/user_repository.go` | Editar |\n| `internal/service/user_service.go` | Editar |\n| `internal/handler/grpc/user_handler.go` | Editar |\n| `internal/service/user_service_test.go` | Editar |\n| `internal/handler/grpc/user_handler_test.go` | Editar |\n\n---\n\n## 7. Riscos e Observacoes\n\n1. **Tipo SQLC para coluna nullable:** A coluna `telefone` e nullable (sem NOT NULL), entao o SQLC gerara `sql.NullString`. O mapeamento no repository precisa tratar isso corretamente (NullString <-> string).\n2. **Migracao automatica:** A migracao executa no boot da aplicacao. Verificar que o numero sequencial 003 esta correto e nao conflita com outras migracoes em desenvolvimento.\n3. **Compatibilidade retroativa do proto:** Adicionar campos com novos field numbers (7, 5, 6) e retrocompativel com clientes existentes. Clientes antigos simplesmente ignorarao o campo `phone`.\n4. **Testes de integracao:** O arquivo `internal/e2e/user_e2e_test.go` pode precisar de atualizacao, mas nao foi incluido no escopo da SPEC_TECH. Avaliar se e necessario.\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 7 tasks (TASK-1 a TASK-7). Excede o limite de 6 para uma alteracao pontual."}, {"text": "Respeita a ordem de dependencias das camadas (migracao > SQLC > repository > service > handler > proto)", "passed": true, "evidence": "Ordem: TASK-1(migracao) -> TASK-2(SQLC) -> TASK-3(repository) -> TASK-4(service) -> TASK-5(proto, paralelo) -> TASK-6(handler) -> TASK-7(testes). Dependencias corretas."}, {"text": "Mapeia a US-10 na tabela de rastreabilidade do task_plan", "passed": false, "evidence": "Nao ha tabela de rastreabilidade US -> Tasks no output. US-10 nao e mencionada em nenhuma secao de rastreabilidade formal."}, {"text": "Cada task segue o template completo (secoes 1-8) ou marca secoes N/A com justificativa", "passed": false, "evidence": "Tasks usam formato simplificado (Camada, Arquivo, Descricao, Alteracoes, Observacoes, Criterio de aceite, Dependencias). Nao seguem template de 8 secoes."}, {"text": "Salva task_plan.md como documento de REFERENCIA (sem corpo detalhado de tasks dentro dele)", "passed": false, "evidence": "Nao ha documento task_plan.md separado. Tudo esta em um unico response.md com tasks detalhadas inline."}]}}, {"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 sdd-task-plan-expert\n\n## Nome da Feature Extraido\n\nDa secao \"1. Identificacao\" do SPEC_TECH, campo \"Feature/Projeto\": **Cache de Cardapio em Memoria**\n\nNome em kebab-case para diretorio: `cache-cardapio`\n\n---\n\n## Analise Obrigatoria do Projeto (Resultados)\n\n### Passo 1: Rules lidas\n\nLi todos os arquivos de regras do projeto:\n- `CLAUDE.md` (raiz)\n- `.claude/rules/code-conventions.md` \u00e2\u20ac\u201d codigo em ingles, banco/logs/erros em portugues\n- `.claude/rules/database.md` \u00e2\u20ac\u201d SQLite WAL, migracoes imutaveis, nomenclatura em portugues\n- `.claude/rules/fx-di.md` \u00e2\u20ac\u201d Uber FX, modulos exportados via `var Module = fx.Module(...)`\n- `.claude/rules/grpc.md` \u00e2\u20ac\u201d handlers, mapeamento de erros, registro no server.go\n- `.claude/rules/protobuf.md` \u00e2\u20ac\u201d Buf, convencao de go_package\n- `.claude/rules/repository.md` \u00e2\u20ac\u201d interface + implementacao nao exportada, construtor recebe `*sql.DB`, mapeamento portugues->ingles\n- `.claude/rules/service.md` \u00e2\u20ac\u201d interface + implementacao nao exportada, erros exportados em portugues, logging com zap\n- `.claude/rules/sqlc.md` \u00e2\u20ac\u201d queries em `internal/db/queries/`, codigo gerado em `internal/db/sqlc/` (nao editar)\n\n### Passo 2: Codebase explorado\n\n**Descobertas criticas para o planejamento:**\n\n1. **NAO existe ProductService, ProductRepository, nem nenhum codigo de produto/cardapio no projeto.** O unico modulo implementado e o de User (service, repository, handler, migrations, queries).\n\n2. **NAO existe `internal/infra/cache/`** \u00e2\u20ac\u201d o pacote de cache precisa ser criado do zero.\n\n3. **NAO existe campo `cache` no `configs/config.yaml`** nem na struct `Config` (`internal/infra/config/config.go`). Atualmente a struct tem apenas: `GRPCHost`, `GRPCPort`, `DatabasePath`, `JWTSecret`, `JWTExpirationHours`, `LogLevel`.\n\n4. **Padroes estabelecidos identificados:**\n - FX modules: cada pacote exporta `var Module = fx.Module(\"nome\", ...)`\n - DI composition: `internal/infra/di/fx.go` lista todos os modulos\n - Service pattern: interface publica + implementacao nao exportada + construtor `NewXxxService(repo, cfg, logger)`\n - Repository pattern: interface publica + implementacao nao exportada + construtor `NewXxxRepository(db *sql.DB)`\n - Testes: table-driven com testify/mock, mock definido no mesmo arquivo de teste\n - Config: Viper com env prefix `APP_`, override via variaveis de ambiente\n\n5. **Tabelas existentes no banco:** `usuarios` (migracoes 001, 002). Nenhuma tabela `produtos`.\n\n---\n\n## Problema Identificado: SPEC_TECH sem User Stories e sem PRD\n\nAntes de prosseguir, preciso levantar uma questao importante.\n\nO SPEC_TECH fornecido **nao contem User Stories (US-XX)** e **nao referencia nenhum PRD**. No framework SDD, o TASK PLAN exige uma tabela de rastreabilidade mapeando User Stories do PRD para Tasks. Alem disso, o guardrail #8 do skill diz: \"Rastreabilidade obrigatoria \u00e2\u20ac\u201d toda User Story do PRD deve ter pelo menos uma task correspondente.\"\n\nAlem disso, o SPEC_TECH menciona que \"ProductService passa a consultar cache antes do repository\", mas **nao existe nenhum ProductService no projeto atual**. O cache seria aplicado sobre um servico que ainda nao foi implementado.\n\n### Pergunta que eu faria ao usuario:\n\n> **Pergunta:** Identifiquei duas questoes no SPEC_TECH que precisam de esclarecimento antes de iniciarmos o TASK PLAN:\n>\n> 1. **Ausencia de User Stories e PRD:** O SPEC_TECH nao contem User Stories (US-XX) nem referencia um PRD. O framework SDD exige rastreabilidade US -> Tasks no TASK PLAN. Como deseja proceder?\n> - **Opcao A:** Voce fornece o PRD com as User Stories para que eu faca a rastreabilidade completa.\n> - **Opcao B:** Criamos o TASK PLAN sem a tabela de rastreabilidade US -> Tasks, marcando a secao como \"N/A \u00e2\u20ac\u201d SPEC_TECH sem PRD/User Stories associados\". As tasks serao derivadas diretamente das Definicoes Tecnicas do SPEC_TECH.\n> - **Opcao C:** Definimos User Stories simplificadas agora, antes de iniciar o planejamento.\n>\n> 2. **ProductService inexistente:** O SPEC_TECH assume que existe um `ProductService` que \"passa a consultar cache antes do repository\", mas nao existe nenhum codigo de produto no projeto (nem service, nem repository, nem migration, nem queries). O escopo deste TASK PLAN deve:\n> - **Opcao A:** Incluir a criacao completa do modulo de produtos (migration, queries, repository, service, handler, proto) ALEM do cache \u00e2\u20ac\u201d tornando o TASK PLAN significativamente maior.\n> - **Opcao B:** Limitar-se apenas a infraestrutura de cache generica (`Cache[K, V]`, config, modulo FX), sem integrar com ProductService, deixando a integracao para quando o modulo de produtos existir.\n> - **Opcao C:** Voce tem outro SPEC_TECH ou feature de produtos ja planejada que deve ser executada antes?\n>\n> Aguardo sua resposta para prosseguir.\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": "Explicitamente diz 'O SPEC_TECH fornecido nao contem User Stories (US-XX) e nao referencia nenhum PRD' na secao 'Problema Identificado'."}, {"text": "PERGUNTA ao usuario sobre User Stories / PRD em vez de inventar ou ignorar", "passed": true, "evidence": "Faz pergunta com 3 opcoes (A: fornecer PRD, B: criar sem rastreabilidade, C: definir US simplificadas) e aguarda resposta."}, {"text": "NAO inventa User Stories ficticias (guardrail: nunca invente informacoes)", "passed": true, "evidence": "Nenhuma User Story ficticia foi criada. Apenas identificou a ausencia e perguntou ao usuario como proceder."}, {"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-cardapio' em kebab-case."}, {"text": "Segue o processo interativo (uma pergunta por vez, confirma nome antes de prosseguir)", "passed": true, "evidence": "Apresentou o nome extraido e fez UMA pergunta (sobre US/PRD + ProductService) aguardando resposta antes de prosseguir."}]}}, {"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-08 |\n| **SPEC_TECH** | Cache de Cardapio v1 |\n| **Branch base** | user-feature |\n\n---\n\n## 2. Observacoes Preliminares\n\nO SPEC_TECH fornecido nao referencia User Stories nem PRD. Consequencias praticas:\n\n- Os criterios de aceite de cada task serao derivados exclusivamente das definicoes tecnicas do SPEC_TECH.\n- Nao ha requisitos funcionais de negocio formalizados (ex: \"como garcom, quero ver o cardapio rapidamente\"). A motivacao implicita e performance: evitar queries repetidas ao SQLite para leitura do cardapio.\n- **O codebase atual nao possui modulo de Product/Produto.** Apenas o modulo User esta implementado. O SPEC_TECH menciona \"ProductService\" como ponto de integracao do cache, portanto o modulo Product deve existir antes das tasks de integracao. Este task plan trata isso como pre-requisito explicito.\n\n---\n\n## 3. Pre-requisitos\n\n| # | Pre-requisito | Justificativa | Status |\n|---|---------------|---------------|--------|\n| P1 | Modulo Product completo (proto, migration, queries SQLC, repository, service, handler gRPC) | O cache sera integrado ao ProductService que ainda nao existe | Pendente |\n| P2 | Codigo SQLC gerado para produtos (`make sqlc`) | Repository de produtos depende do codigo gerado | Pendente |\n| P3 | Codigo Proto gerado para produtos (`make proto`) | Handler gRPC de produtos depende do codigo gerado | Pendente |\n\n> Tasks 1 e 2 (infraestrutura de cache e config) sao independentes dos pre-requisitos e podem ser iniciadas imediatamente. Tasks 3 a 5 dependem de P1.\n\n---\n\n## 4. Tasks\n\n### Task 1 -- Configuracao de cache (config.yaml + struct Config)\n\n**Objetivo:** Tornar o TTL do cache configuravel via `config.yaml` e variaveis de ambiente.\n\n**Arquivos afetados:**\n- `configs/config.yaml` (modificar)\n- `internal/infra/config/config.go` (modificar)\n\n**Subtasks:**\n\n| # | Descricao | Detalhe |\n|---|-----------|---------|\n| 1.1 | Adicionar secao `cache` ao config.yaml | `cache:` com `ttl_seconds: 300` |\n| 1.2 | Adicionar campo `CacheTTLSeconds int` na struct `Config` | Em `internal/infra/config/config.go` |\n| 1.3 | Popular o campo no construtor `NewConfig` | `CacheTTLSeconds: v.GetInt(\"cache.ttl_seconds\")`. Se valor for 0 (nao configurado), aplicar fallback para 300 |\n| 1.4 | Teste unitario | Verificar valor padrao 300, override via YAML e override via env var `APP_CACHE_TTL_SECONDS` |\n\n**Criterios de aceite:**\n- `Config.CacheTTLSeconds` retorna 300 quando a secao `cache` nao esta presente no YAML.\n- Override via YAML e via variavel de ambiente funciona corretamente.\n- Testes passam com `CGO_ENABLED=1 go test ./internal/infra/config/...`.\n\n**Estimativa:** P (pequena)\n\n---\n\n### Task 2 -- Pacote de cache generico com sync.Map\n\n**Objetivo:** Criar `internal/infra/cache/` com interface generica e implementacao in-memory com TTL.\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 (placeholder)\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 | Definir struct interna `cacheEntry[V any]` | Campos: `value V`, `createdAt time.Time` |\n| 2.3 | Implementar `InMemoryCache[K, V]` | Usar `sync.Map` como storage. No `Get`, verificar `time.Since(entry.createdAt) > ttl`; se expirado, deletar e retornar zero value + false |\n| 2.4 | Construtor `NewInMemoryCache[K, V](ttl time.Duration) Cache[K, V]` | Retornar ponteiro para InMemoryCache |\n| 2.5 | Testes unitarios | Cenarios: Get/Set basico, expiracao por TTL (usar TTL de 50ms + time.Sleep), Invalidate chave unica, InvalidateAll, Get de chave inexistente |\n| 2.6 | Modulo FX | Criar `fx.go` com modulo vazio por enquanto; a factory concreta sera adicionada na Task 3 |\n\n**Decisoes tecnicas:**\n- `sync.Map` e thread-safe e otimizado para leitura frequente, adequado para cache de cardapio.\n- Verificacao de TTL e lazy (no `Get`), sem goroutine de limpeza. Aceitavel em v1 para cardapio com poucos itens.\n- Generics do Go 1.24 permitem type safety sem type assertions manuais.\n\n**Criterios de aceite:**\n- Interface `Cache[K, V]` exportada e documentada.\n- `InMemoryCache` implementa todos os metodos da interface.\n- Itens expirados nao sao retornados por `Get`.\n- `Invalidate` remove entrada especifica; `InvalidateAll` limpa tudo.\n- Testes passam, incluindo cenario de expiracao.\n\n**Estimativa:** M (media)\n\n---\n\n### Task 3 -- Registro do cache no sistema de DI (Uber FX)\n\n**Objetivo:** Criar factory function para cache de produtos e registrar no grafo de dependencias.\n\n**Arquivos afetados:**\n- `internal/infra/cache/fx.go` (modificar -- criado na Task 2)\n- `internal/infra/di/fx.go` (modificar)\n\n**Subtasks:**\n\n| # | Descricao | Detalhe |\n|---|-----------|---------|\n| 3.1 | Criar factory `NewProductCache` | `func NewProductCache(cfg *config.Config) Cache[string, *repository.Product]` -- converte `cfg.CacheTTLSeconds` para `time.Duration` e chama `NewInMemoryCache` |\n| 3.2 | Registrar provider no modulo FX | `fx.Provide(NewProductCache)` dentro de `cache.Module` |\n| 3.3 | Importar `cache.Module` em `internal/infra/di/fx.go` | Posicionar apos `config.Module` e antes de `repository.Module` |\n| 3.4 | Verificar inicializacao | Rodar `fx.ValidateApp` ou iniciar a aplicacao para confirmar que o grafo resolve sem erros |\n\n**Dependencias:** Task 1, Task 2, Pre-requisito P1 (tipo `repository.Product` deve existir)\n\n**Criterios de aceite:**\n- `cache.Module` registrado em `AppModule()`.\n- Aplicacao inicializa sem erros de resolucao de dependencias.\n- Factory usa o TTL do config (nao hardcoded).\n\n**Estimativa:** P (pequena)\n\n---\n\n### Task 4 -- Integracao do cache no ProductService\n\n**Objetivo:** Implementar padrao cache-aside no ProductService: consultar cache antes do repository nas leituras, e invalidar cache nas escritas.\n\n**Arquivos afetados:**\n- `internal/service/product_service.go` (modificar)\n- `internal/service/product_service_test.go` (modificar)\n\n**Subtasks:**\n\n| # | Descricao | Detalhe |\n|---|-----------|---------|\n| 4.1 | Adicionar campo `cache` na struct do service | `cache cache.Cache[string, *repository.Product]` |\n| 4.2 | Atualizar construtor `NewProductService` | Adicionar parametro `cache cache.Cache[string, *repository.Product]` |\n| 4.3 | Implementar cache-aside no `GetProduct` (ou `GetProductByID`) | 1) `cache.Get(id)` -- se hit, logar e retornar. 2) Se miss, `repo.GetByID(id)`, `cache.Set(id, product)`, retornar. |\n| 4.4 | Invalidar cache no `CreateProduct` | Apos criacao no repository com sucesso, **nao popular o cache** (evita inconsistencia com listagens). |\n| 4.5 | Invalidar cache no `UpdateProduct` | `cache.Invalidate(id)` apos update no repository |\n| 4.6 | Invalidar cache no `DeleteProduct` | `cache.Invalidate(id)` apos delete no repository |\n| 4.7 | Adicionar logs de cache | `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 | Testes unitarios com mock de cache | Cenarios: (a) cache hit -- repo nao e chamado, (b) cache miss -- repo e chamado e cache e populado, (c) create/update/delete invalidam cache |\n\n**Dependencias:** Task 3, Pre-requisito P1\n\n**Criterios de aceite:**\n- `GetProduct` retorna do cache em cache hit sem chamar o repository.\n- `GetProduct` busca do repository em cache miss, popula o cache, e retorna.\n- Operacoes de escrita (`Create`, `Update`, `Delete`) invalidam a entrada correspondente.\n- Logs de debug indicam hit/miss.\n- Testes unitarios passam com mocks para cache e repository.\n- Nenhuma regressao nos testes existentes do modulo.\n\n**Estimativa:** M (media)\n\n---\n\n### Task 5 -- Testes de integracao e validacao final\n\n**Objetivo:** Validar o comportamento end-to-end do cache, incluindo TTL, concorrencia e ausencia de regressoes.\n\n**Arquivos afetados:**\n- `internal/service/product_service_test.go` (adicionar testes)\n\n**Subtasks:**\n\n| # | Descricao | Detalhe |\n|---|-----------|---------|\n| 5.1 | Teste de fluxo completo | Create -> Get (miss, popula) -> Get (hit) -> Update (invalida) -> Get (miss novamente) |\n| 5.2 | Teste de expiracao por TTL | Set com TTL curto (100ms) -> Get (hit) -> Sleep(150ms) -> Get (miss) |\n| 5.3 | Teste de concorrencia (race detection) | Multiplas goroutines fazendo Get/Set simultaneo; rodar com `-race` |\n| 5.4 | Rodar suite completa | `make test` -- todos os testes do projeto devem passar |\n| 5.5 | Validar build | `make build` deve compilar sem erros |\n\n**Dependencias:** Tasks 1 a 4 completas\n\n**Criterios de aceite:**\n- Todos os cenarios de teste passam.\n- `CGO_ENABLED=1 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. Grafo de 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- **Paralelizaveis:** Tasks 1 e 2 (sem dependencia entre si).\n- **Sequenciais:** Task 3 depende de 1 + 2. Task 4 depende de 3. Task 5 depende de 4.\n- **Bloqueio externo:** Tasks 3, 4 e 5 dependem do pre-requisito P1 (modulo Product).\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 (assumindo P1 ja resolvido).\n\n---\n\n## 7. Riscos e Mitigacoes\n\n| # | Risco | Impacto | Mitigacao |\n|---|-------|---------|-----------|\n| R1 | Modulo Product nao existe -- bloqueia Tasks 3-5 | Alto | Tasks 1 e 2 sao independentes e podem avancar. Priorizar implementacao do modulo Product. |\n| R2 | `sync.Map` usa `any` internamente, exigindo type assertions | Baixo | Wrapper generico (`InMemoryCache[K, V]`) encapsula as assertions. Testes garantem type safety. |\n| R3 | Lazy expiration pode acumular entradas expiradas em memoria | Baixo | Cardapio tem poucos itens (dezenas). Aceitavel em v1. Considerar cleanup periodico em v2. |\n| R4 | Dados stale em escrita concorrente (ex: dois updates simultaneos) | Baixo | Invalidacao sincrona no fluxo de escrita. TTL de 300s garante convergencia eventual. |\n| R5 | Ausencia de metricas (hit rate, tamanho do cache) | Baixo | Fora de escopo em v1. Logs de debug permitem analise manual. Considerar metricas em v2. |\n| R6 | Sem PRD/User Stories -- criterios de aceite podem nao cobrir expectativas do negocio | Medio | Criterios derivados do SPEC_TECH. Validar com stakeholders se ha requisitos funcionais adicionais. |\n\n---\n\n## 8. Arquivos Criados e Modificados\n\n### Novos arquivos:\n| Arquivo | Task |\n|---------|------|\n| `internal/infra/cache/cache.go` | Task 2 |\n| `internal/infra/cache/cache_test.go` | Task 2 |\n| `internal/infra/cache/fx.go` | Tasks 2, 3 |\n\n### Arquivos modificados:\n| Arquivo | Task | Alteracao |\n|---------|------|-----------|\n| `configs/config.yaml` | Task 1 | Adicionar secao `cache` |\n| `internal/infra/config/config.go` | Task 1 | Adicionar campo `CacheTTLSeconds` |\n| `internal/infra/di/fx.go` | Task 3 | Importar e registrar `cache.Module` |\n| `internal/service/product_service.go` | Task 4 | Adicionar dependencia de cache e logica cache-aside |\n| `internal/service/product_service_test.go` | Tasks 4, 5 | Testes de cache hit/miss, TTL, concorrencia |\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 diz 'O SPEC_TECH fornecido nao referencia User Stories nem PRD' e documenta as consequencias."}, {"text": "PERGUNTA ao usuario sobre User Stories / PRD em vez de inventar ou ignorar", "passed": false, "evidence": "Nao perguntou ao usuario. Simplesmente notou a ausencia e prosseguiu gerando o task plan completo com 5 tasks, tratando como observacao."}, {"text": "NAO inventa User Stories ficticias (guardrail: nunca invente informacoes)", "passed": true, "evidence": "Nenhuma User Story ficticia foi criada. Criterios derivados das definicoes tecnicas."}, {"text": "Extrai corretamente o nome da feature como 'cache-de-cardapio' ou similar em kebab-case", "passed": false, "evidence": "Usou 'Cache de Cardapio v1' como titulo mas nao extraiu nome em kebab-case."}, {"text": "Segue o processo interativo (uma pergunta por vez, confirma nome antes de prosseguir)", "passed": false, "evidence": "Gerou o task plan completo sem nenhuma interacao. Nao confirmou nome, nao perguntou sobre fases, nao fez processo iterativo."}]}}], "previous_feedback": {}, "previous_outputs": {"eval-1-happy-path-with_skill": [{"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"}], "eval-1-happy-path-without_skill": [{"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"}], "eval-2-spec-simples-with_skill": [{"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"}], "eval-2-spec-simples-without_skill": [{"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"}], "eval-3-sem-user-stories-with_skill": [{"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"}], "eval-3-sem-user-stories-without_skill": [{"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"}]}, "benchmark": {"skill_name": "sdd-task-plan-expert", "iteration": 2, "date": "2026-03-08", "configurations": [{"name": "with_skill", "evals": [{"eval_id": 1, "eval_name": "happy-path-spec-tech-usuario", "assertions_passed": 5, "assertions_total": 5, "pass_rate": 1.0, "tokens": 64193, "duration_seconds": 125.6}, {"eval_id": 2, "eval_name": "spec-tech-simples-alteracao-pontual", "assertions_passed": 5, "assertions_total": 5, "pass_rate": 1.0, "tokens": 85600, "duration_seconds": 305.6}, {"eval_id": 3, "eval_name": "spec-tech-sem-user-stories-explicitas", "assertions_passed": 5, "assertions_total": 5, "pass_rate": 1.0, "tokens": 58132, "duration_seconds": 136.9}], "aggregate": {"total_passed": 15, "total_assertions": 15, "pass_rate": 1.0, "mean_tokens": 69308, "stddev_tokens": 14283, "mean_duration_seconds": 189.4, "stddev_duration_seconds": 101.4}}, {"name": "without_skill", "evals": [{"eval_id": 1, "eval_name": "happy-path-spec-tech-usuario", "assertions_passed": 1, "assertions_total": 5, "pass_rate": 0.2, "tokens": 69814, "duration_seconds": 347.3}, {"eval_id": 2, "eval_name": "spec-tech-simples-alteracao-pontual", "assertions_passed": 1, "assertions_total": 5, "pass_rate": 0.2, "tokens": 61206, "duration_seconds": 143.3}, {"eval_id": 3, "eval_name": "spec-tech-sem-user-stories-explicitas", "assertions_passed": 2, "assertions_total": 5, "pass_rate": 0.4, "tokens": 36808, "duration_seconds": 147.5}], "aggregate": {"total_passed": 4, "total_assertions": 15, "pass_rate": 0.267, "mean_tokens": 55943, "stddev_tokens": 17257, "mean_duration_seconds": 212.7, "stddev_duration_seconds": 117.2}}], "delta": {"pass_rate": "+73.3pp", "mean_tokens": "+13365 (+23.9%)", "mean_duration_seconds": "-23.3s (-11.0%)"}}};
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>