reviewflow 3.23.0 → 3.24.1

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 (71) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/dist/dashboard/index.html +30 -6
  3. package/dist/dashboard/modules/desktopNotifications.d.ts +7 -1
  4. package/dist/dashboard/modules/desktopNotifications.d.ts.map +1 -1
  5. package/dist/dashboard/modules/desktopNotifications.js +194 -15
  6. package/dist/dashboard/modules/desktopNotifications.js.map +1 -1
  7. package/dist/dashboard/modules/i18n.d.ts.map +1 -1
  8. package/dist/dashboard/modules/i18n.js +22 -2
  9. package/dist/dashboard/modules/i18n.js.map +1 -1
  10. package/dist/dashboard/modules/settingsModal.d.ts +17 -0
  11. package/dist/dashboard/modules/settingsModal.d.ts.map +1 -1
  12. package/dist/dashboard/modules/settingsModal.js +33 -0
  13. package/dist/dashboard/modules/settingsModal.js.map +1 -1
  14. package/dist/frameworks/claude/claudeInvoker.d.ts +2 -0
  15. package/dist/frameworks/claude/claudeInvoker.d.ts.map +1 -1
  16. package/dist/frameworks/claude/claudeInvoker.js +15 -3
  17. package/dist/frameworks/claude/claudeInvoker.js.map +1 -1
  18. package/dist/frameworks/queue/pQueueAdapter.d.ts +27 -0
  19. package/dist/frameworks/queue/pQueueAdapter.d.ts.map +1 -1
  20. package/dist/frameworks/queue/pQueueAdapter.js +4 -0
  21. package/dist/frameworks/queue/pQueueAdapter.js.map +1 -1
  22. package/dist/modules/claude-invocation/usecases/retrieveReviewReport.usecase.d.ts +1 -0
  23. package/dist/modules/claude-invocation/usecases/retrieveReviewReport.usecase.d.ts.map +1 -1
  24. package/dist/modules/claude-invocation/usecases/retrieveReviewReport.usecase.js +13 -5
  25. package/dist/modules/claude-invocation/usecases/retrieveReviewReport.usecase.js.map +1 -1
  26. package/dist/modules/claude-invocation/usecases/runClaudeReviewJob.usecase.d.ts +1 -0
  27. package/dist/modules/claude-invocation/usecases/runClaudeReviewJob.usecase.d.ts.map +1 -1
  28. package/dist/modules/claude-invocation/usecases/runClaudeReviewJob.usecase.js +1 -0
  29. package/dist/modules/claude-invocation/usecases/runClaudeReviewJob.usecase.js.map +1 -1
  30. package/dist/modules/cli-configuration/interface-adapters/controllers/http/projectConfig.routes.d.ts.map +1 -1
  31. package/dist/modules/cli-configuration/interface-adapters/controllers/http/projectConfig.routes.js +13 -0
  32. package/dist/modules/cli-configuration/interface-adapters/controllers/http/projectConfig.routes.js.map +1 -1
  33. package/dist/modules/cli-configuration/usecases/projectConfig/updateProjectConfig.usecase.d.ts +4 -2
  34. package/dist/modules/cli-configuration/usecases/projectConfig/updateProjectConfig.usecase.d.ts.map +1 -1
  35. package/dist/modules/cli-configuration/usecases/projectConfig/updateProjectConfig.usecase.js +21 -0
  36. package/dist/modules/cli-configuration/usecases/projectConfig/updateProjectConfig.usecase.js.map +1 -1
  37. package/dist/modules/platform-integration/entities/github/githubPullRequestEvent.guard.d.ts +12 -0
  38. package/dist/modules/platform-integration/entities/github/githubPullRequestEvent.guard.d.ts.map +1 -1
  39. package/dist/modules/platform-integration/entities/github/githubPullRequestEvent.guard.js +4 -0
  40. package/dist/modules/platform-integration/entities/github/githubPullRequestEvent.guard.js.map +1 -1
  41. package/dist/modules/platform-integration/interface-adapters/controllers/webhook/github.controller.d.ts.map +1 -1
  42. package/dist/modules/platform-integration/interface-adapters/controllers/webhook/github.controller.js +15 -0
  43. package/dist/modules/platform-integration/interface-adapters/controllers/webhook/github.controller.js.map +1 -1
  44. package/dist/modules/platform-integration/interface-adapters/controllers/webhook/gitlab.controller.d.ts.map +1 -1
  45. package/dist/modules/platform-integration/interface-adapters/controllers/webhook/gitlab.controller.js +6 -0
  46. package/dist/modules/platform-integration/interface-adapters/controllers/webhook/gitlab.controller.js.map +1 -1
  47. package/dist/modules/worktree-management/entities/gitCommand/gitCommand.gateway.d.ts +1 -1
  48. package/dist/modules/worktree-management/entities/gitCommand/gitCommand.gateway.d.ts.map +1 -1
  49. package/dist/modules/worktree-management/services/claudeCwd.d.ts +15 -0
  50. package/dist/modules/worktree-management/services/claudeCwd.d.ts.map +1 -0
  51. package/dist/modules/worktree-management/services/claudeCwd.js +45 -0
  52. package/dist/modules/worktree-management/services/claudeCwd.js.map +1 -0
  53. package/dist/tests/stubs/reviewReport.stub.d.ts +2 -0
  54. package/dist/tests/stubs/reviewReport.stub.d.ts.map +1 -1
  55. package/dist/tests/stubs/reviewReport.stub.js +8 -0
  56. package/dist/tests/stubs/reviewReport.stub.js.map +1 -1
  57. package/dist/tests/units/dashboard/modules/desktopNotifications.test.js +230 -13
  58. package/dist/tests/units/dashboard/modules/desktopNotifications.test.js.map +1 -1
  59. package/dist/tests/units/dashboard/modules/settingsModal.test.js +106 -2
  60. package/dist/tests/units/dashboard/modules/settingsModal.test.js.map +1 -1
  61. package/dist/tests/units/modules/claude-invocation/usecases/retrieveReviewReport.usecase.test.js +52 -0
  62. package/dist/tests/units/modules/claude-invocation/usecases/retrieveReviewReport.usecase.test.js.map +1 -1
  63. package/dist/tests/units/modules/cli-configuration/interface-adapters/controllers/http/projectConfig.routes.test.js +39 -0
  64. package/dist/tests/units/modules/cli-configuration/interface-adapters/controllers/http/projectConfig.routes.test.js.map +1 -1
  65. package/dist/tests/units/modules/cli-configuration/usecases/projectConfig/updateProjectConfig.usecase.test.js +69 -0
  66. package/dist/tests/units/modules/cli-configuration/usecases/projectConfig/updateProjectConfig.usecase.test.js.map +1 -1
  67. package/dist/tests/units/modules/worktree-management/services/claudeCwd.test.d.ts +2 -0
  68. package/dist/tests/units/modules/worktree-management/services/claudeCwd.test.d.ts.map +1 -0
  69. package/dist/tests/units/modules/worktree-management/services/claudeCwd.test.js +111 -0
  70. package/dist/tests/units/modules/worktree-management/services/claudeCwd.test.js.map +1 -0
  71. package/package.json +1 -1
@@ -3,14 +3,22 @@ export class StubReviewReportGateway {
3
3
  content: '# Stub report',
4
4
  path: '/tmp/project/.claude/reviews/2026-05-22-MR-42-review.md',
5
5
  };
6
+ readStrategy = null;
6
7
  lastReadJobType = null;
7
8
  readCallCount = 0;
8
9
  setReport(report) {
9
10
  this.report = report;
11
+ this.readStrategy = null;
12
+ }
13
+ setReadStrategy(strategy) {
14
+ this.readStrategy = strategy;
10
15
  }
11
16
  read(location) {
12
17
  this.lastReadJobType = location.jobType;
13
18
  this.readCallCount += 1;
19
+ if (this.readStrategy !== null) {
20
+ return this.readStrategy(location);
21
+ }
14
22
  return this.report;
15
23
  }
16
24
  buildPath(location) {
@@ -1 +1 @@
1
- {"version":3,"file":"reviewReport.stub.js","sourceRoot":"","sources":["../../../src/tests/stubs/reviewReport.stub.ts"],"names":[],"mappings":"AAOA,MAAM,OAAO,uBAAuB;IAC1B,MAAM,GAA+B;QAC3C,OAAO,EAAE,eAAe;QACxB,IAAI,EAAE,yDAAyD;KAChE,CAAC;IAEF,eAAe,GAAgC,IAAI,CAAC;IACpD,aAAa,GAAG,CAAC,CAAC;IAElB,SAAS,CAAC,MAAkC;QAC1C,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;IACvB,CAAC;IAED,IAAI,CAAC,QAA8B;QACjC,IAAI,CAAC,eAAe,GAAG,QAAQ,CAAC,OAAO,CAAC;QACxC,IAAI,CAAC,aAAa,IAAI,CAAC,CAAC;QACxB,OAAO,IAAI,CAAC,MAAM,CAAC;IACrB,CAAC;IAED,SAAS,CAAC,QAA8B;QACtC,MAAM,MAAM,GAAG,QAAQ,CAAC,OAAO,KAAK,UAAU,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,QAAQ,CAAC;QACvE,OAAO,GAAG,QAAQ,CAAC,SAAS,oBAAoB,QAAQ,CAAC,OAAO,OAAO,QAAQ,CAAC,kBAAkB,IAAI,MAAM,KAAK,CAAC;IACpH,CAAC;CACF"}
1
+ {"version":3,"file":"reviewReport.stub.js","sourceRoot":"","sources":["../../../src/tests/stubs/reviewReport.stub.ts"],"names":[],"mappings":"AAOA,MAAM,OAAO,uBAAuB;IAC1B,MAAM,GAA+B;QAC3C,OAAO,EAAE,eAAe;QACxB,IAAI,EAAE,yDAAyD;KAChE,CAAC;IACM,YAAY,GAA4E,IAAI,CAAC;IAErG,eAAe,GAAgC,IAAI,CAAC;IACpD,aAAa,GAAG,CAAC,CAAC;IAElB,SAAS,CAAC,MAAkC;QAC1C,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC;IAC3B,CAAC;IAED,eAAe,CAAC,QAAwE;QACtF,IAAI,CAAC,YAAY,GAAG,QAAQ,CAAC;IAC/B,CAAC;IAED,IAAI,CAAC,QAA8B;QACjC,IAAI,CAAC,eAAe,GAAG,QAAQ,CAAC,OAAO,CAAC;QACxC,IAAI,CAAC,aAAa,IAAI,CAAC,CAAC;QACxB,IAAI,IAAI,CAAC,YAAY,KAAK,IAAI,EAAE,CAAC;YAC/B,OAAO,IAAI,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAC;QACrC,CAAC;QACD,OAAO,IAAI,CAAC,MAAM,CAAC;IACrB,CAAC;IAED,SAAS,CAAC,QAA8B;QACtC,MAAM,MAAM,GAAG,QAAQ,CAAC,OAAO,KAAK,UAAU,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,QAAQ,CAAC;QACvE,OAAO,GAAG,QAAQ,CAAC,SAAS,oBAAoB,QAAQ,CAAC,OAAO,OAAO,QAAQ,CAAC,kBAAkB,IAAI,MAAM,KAAK,CAAC;IACpH,CAAC;CACF"}
@@ -25,25 +25,242 @@ describe('shouldNotifyDesktop', () => {
25
25
  });
26
26
  });
27
27
  describe('getDesktopNotificationPayload', () => {
28
- const translate = (key, params) => {
29
- if (key === 'notify.desktopTitle')
30
- return 'Reviewflow alert';
31
- if (key === 'notify.reviewStarted')
32
- return `Review started for !${params?.mrNumber}`;
33
- return key;
28
+ const translate = (key) => {
29
+ const dictionary = {
30
+ 'notify.label.reviewStarted': 'Review',
31
+ 'notify.label.followupStarted': 'Follow-up',
32
+ 'notify.label.reviewCompleted': 'Review done',
33
+ 'notify.label.followupCompleted': 'Follow-up done',
34
+ 'notify.label.reviewFailed': 'Review failed',
35
+ 'notify.label.reviewPendingConfirmation': 'Awaiting confirmation',
36
+ };
37
+ return dictionary[key] ?? key;
34
38
  };
35
- it('should map known notification kind to desktop payload', () => {
39
+ it('builds a rich payload with emoji, author, project and title for reviewStarted', () => {
36
40
  const result = getDesktopNotificationPayload({
37
41
  kind: 'reviewStarted',
38
- review: { mrNumber: 42 },
42
+ review: {
43
+ mrNumber: 1234,
44
+ title: 'feat(dashboard): add chart',
45
+ project: 'main-app-v3/frontend',
46
+ assignedBy: { displayName: 'Damien', username: 'dgouron' },
47
+ id: 'gitlab-main-app-v3-1234',
48
+ jobType: 'review',
49
+ },
39
50
  }, translate);
40
- expect(result).toEqual({
41
- title: 'Reviewflow alert',
42
- body: 'Review started for !42',
43
- tag: 'reviewflow-reviewStarted-42',
51
+ expect(result).toMatchObject({
52
+ title: '🔍 Review · Damien · !1234',
53
+ body: 'feat(dashboard): add chart\nfrontend',
54
+ tag: 'reviewflow-reviewStarted-1234',
44
55
  });
45
56
  });
46
- it('should return null for unknown notification kind', () => {
57
+ it('uses # prefix and 🔁 emoji for github followupStarted', () => {
58
+ const result = getDesktopNotificationPayload({
59
+ kind: 'followupStarted',
60
+ review: {
61
+ mrNumber: 42,
62
+ title: 'fix(auth): token refresh',
63
+ project: 'org/repo',
64
+ assignedBy: { displayName: 'Alice' },
65
+ id: 'github-org-repo-42',
66
+ jobType: 'followup',
67
+ },
68
+ }, translate);
69
+ expect(result).toMatchObject({
70
+ title: '🔁 Follow-up · Alice · #42',
71
+ body: 'fix(auth): token refresh\nrepo',
72
+ tag: 'reviewflow-followupStarted-42',
73
+ });
74
+ });
75
+ it('falls back to username when displayName is missing', () => {
76
+ const result = getDesktopNotificationPayload({
77
+ kind: 'reviewStarted',
78
+ review: {
79
+ mrNumber: 7,
80
+ title: 'chore: bump deps',
81
+ project: 'org/repo',
82
+ assignedBy: { username: 'bob' },
83
+ id: 'gitlab-x',
84
+ },
85
+ }, translate);
86
+ expect(result?.title).toBe('🔍 Review · bob · !7');
87
+ });
88
+ it('omits author segment when assignedBy is missing', () => {
89
+ const result = getDesktopNotificationPayload({
90
+ kind: 'reviewStarted',
91
+ review: {
92
+ mrNumber: 9,
93
+ title: 'docs: update README',
94
+ project: 'org/repo',
95
+ id: 'gitlab-x',
96
+ },
97
+ }, translate);
98
+ expect(result?.title).toBe('🔍 Review · !9');
99
+ });
100
+ it('omits title line when title is missing', () => {
101
+ const result = getDesktopNotificationPayload({
102
+ kind: 'reviewStarted',
103
+ review: {
104
+ mrNumber: 11,
105
+ project: 'org/repo',
106
+ assignedBy: { displayName: 'Alice' },
107
+ id: 'gitlab-x',
108
+ },
109
+ }, translate);
110
+ expect(result?.body).toBe('repo');
111
+ });
112
+ it('uses ✅ for reviewCompleted', () => {
113
+ const result = getDesktopNotificationPayload({
114
+ kind: 'reviewCompleted',
115
+ review: {
116
+ mrNumber: 5,
117
+ title: 'feat: add login',
118
+ project: 'org/repo',
119
+ id: 'gitlab-x',
120
+ },
121
+ }, translate);
122
+ expect(result?.title.startsWith('✅ Review done')).toBe(true);
123
+ });
124
+ it('uses ❌ for reviewFailed', () => {
125
+ const result = getDesktopNotificationPayload({
126
+ kind: 'reviewFailed',
127
+ review: { mrNumber: 6, id: 'gitlab-x' },
128
+ }, translate);
129
+ expect(result?.title.startsWith('❌ Review failed')).toBe(true);
130
+ });
131
+ it('uses ⏳ for reviewPendingConfirmation', () => {
132
+ const result = getDesktopNotificationPayload({
133
+ kind: 'reviewPendingConfirmation',
134
+ review: { mrNumber: 8, id: 'gitlab-x' },
135
+ }, translate);
136
+ expect(result?.title.startsWith('⏳ Awaiting confirmation')).toBe(true);
137
+ });
138
+ it('uses author.displayName in title when present (priority over assignedBy)', () => {
139
+ const result = getDesktopNotificationPayload({
140
+ kind: 'reviewStarted',
141
+ review: {
142
+ mrNumber: 100,
143
+ title: 'feat: x',
144
+ project: 'org/repo',
145
+ assignedBy: { displayName: 'Reviewer' },
146
+ author: { displayName: 'Author', username: 'author' },
147
+ id: 'gitlab-x',
148
+ },
149
+ }, translate);
150
+ expect(result?.title).toBe('🔍 Review · Author · !100');
151
+ });
152
+ it('falls back to author.username when displayName missing', () => {
153
+ const result = getDesktopNotificationPayload({
154
+ kind: 'reviewStarted',
155
+ review: {
156
+ mrNumber: 101,
157
+ title: 'feat: x',
158
+ project: 'org/repo',
159
+ author: { username: 'alice' },
160
+ id: 'gitlab-x',
161
+ },
162
+ }, translate);
163
+ expect(result?.title).toBe('🔍 Review · alice · !101');
164
+ });
165
+ it('falls back to assignedBy when author missing', () => {
166
+ const result = getDesktopNotificationPayload({
167
+ kind: 'reviewStarted',
168
+ review: {
169
+ mrNumber: 102,
170
+ project: 'org/repo',
171
+ assignedBy: { displayName: 'Bob' },
172
+ id: 'gitlab-x',
173
+ },
174
+ }, translate);
175
+ expect(result?.title).toBe('🔍 Review · Bob · !102');
176
+ });
177
+ it('renders 🪶 emoji and stats line for small size (additions+deletions < 50)', () => {
178
+ const result = getDesktopNotificationPayload({
179
+ kind: 'reviewStarted',
180
+ review: {
181
+ mrNumber: 200,
182
+ title: 'docs: update',
183
+ project: 'org/repo',
184
+ sizeMetrics: { additions: 20, deletions: 10, filesChanged: 2 },
185
+ id: 'gitlab-x',
186
+ },
187
+ }, translate);
188
+ expect(result?.body).toBe('docs: update\n🪶 +20/-10 · 2 files · repo');
189
+ });
190
+ it('renders 🚀 emoji for medium size (50-300 lines)', () => {
191
+ const result = getDesktopNotificationPayload({
192
+ kind: 'reviewStarted',
193
+ review: {
194
+ mrNumber: 201,
195
+ title: 'feat: middle',
196
+ project: 'org/repo',
197
+ sizeMetrics: { additions: 120, deletions: 45, filesChanged: 8 },
198
+ id: 'gitlab-x',
199
+ },
200
+ }, translate);
201
+ expect(result?.body).toBe('feat: middle\n🚀 +120/-45 · 8 files · repo');
202
+ });
203
+ it('renders 🐘 emoji for big size (>300 lines)', () => {
204
+ const result = getDesktopNotificationPayload({
205
+ kind: 'reviewStarted',
206
+ review: {
207
+ mrNumber: 202,
208
+ title: 'refactor: huge',
209
+ project: 'org/repo',
210
+ sizeMetrics: { additions: 700, deletions: 300, filesChanged: 25 },
211
+ id: 'gitlab-x',
212
+ },
213
+ }, translate);
214
+ expect(result?.body).toBe('refactor: huge\n🐘 +700/-300 · 25 files · repo');
215
+ });
216
+ it('renders size emoji even when additions/deletions are null but filesChanged is known', () => {
217
+ const result = getDesktopNotificationPayload({
218
+ kind: 'reviewStarted',
219
+ review: {
220
+ mrNumber: 203,
221
+ title: 'fix: small',
222
+ project: 'org/repo',
223
+ sizeMetrics: { additions: null, deletions: null, filesChanged: 3 },
224
+ id: 'gitlab-x',
225
+ },
226
+ }, translate);
227
+ expect(result?.body).toBe('fix: small\n🪶 3 files · repo');
228
+ });
229
+ it('includes mrUrl in payload when present', () => {
230
+ const result = getDesktopNotificationPayload({
231
+ kind: 'reviewStarted',
232
+ review: {
233
+ mrNumber: 300,
234
+ mrUrl: 'https://gitlab.example.com/group/repo/-/merge_requests/300',
235
+ id: 'gitlab-x',
236
+ },
237
+ }, translate);
238
+ expect(result?.url).toBe('https://gitlab.example.com/group/repo/-/merge_requests/300');
239
+ });
240
+ it('omits url when mrUrl is missing', () => {
241
+ const result = getDesktopNotificationPayload({
242
+ kind: 'reviewStarted',
243
+ review: { mrNumber: 301, id: 'gitlab-x' },
244
+ }, translate);
245
+ expect(result?.url).toBeNull();
246
+ });
247
+ it('returns a GitLab-colored icon data URI for gitlab platform', () => {
248
+ const result = getDesktopNotificationPayload({
249
+ kind: 'reviewStarted',
250
+ review: { mrNumber: 400, id: 'gitlab-x' },
251
+ }, translate);
252
+ expect(result?.iconDataUri).toMatch(/^data:image\/svg\+xml/);
253
+ expect(result?.iconDataUri).toContain('FC6D26');
254
+ });
255
+ it('returns a GitHub-colored icon data URI for github platform', () => {
256
+ const result = getDesktopNotificationPayload({
257
+ kind: 'reviewStarted',
258
+ review: { mrNumber: 401, id: 'github-org-repo-401' },
259
+ }, translate);
260
+ expect(result?.iconDataUri).toMatch(/^data:image\/svg\+xml/);
261
+ expect(result?.iconDataUri).toContain('181717');
262
+ });
263
+ it('returns null for unknown notification kind', () => {
47
264
  const result = getDesktopNotificationPayload({
48
265
  kind: 'unknown',
49
266
  review: { mrNumber: 1 },
@@ -1 +1 @@
1
- {"version":3,"file":"desktopNotifications.test.js","sourceRoot":"","sources":["../../../../../src/tests/units/dashboard/modules/desktopNotifications.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EACL,6BAA6B,EAC7B,mBAAmB,GACpB,MAAM,6CAA6C,CAAC;AAErD,QAAQ,CAAC,qBAAqB,EAAE,GAAG,EAAE;IACnC,EAAE,CAAC,iEAAiE,EAAE,GAAG,EAAE;QACzE,MAAM,MAAM,GAAG,mBAAmB,CAAC;YACjC,UAAU,EAAE,SAAS;YACrB,gBAAgB,EAAE,IAAI;SACvB,CAAC,CAAC;QAEH,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC5B,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,kDAAkD,EAAE,GAAG,EAAE;QAC1D,MAAM,MAAM,GAAG,mBAAmB,CAAC;YACjC,UAAU,EAAE,SAAS;YACrB,gBAAgB,EAAE,IAAI;SACvB,CAAC,CAAC;QAEH,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAC7B,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,uDAAuD,EAAE,GAAG,EAAE;QAC/D,MAAM,MAAM,GAAG,mBAAmB,CAAC;YACjC,UAAU,EAAE,SAAS;YACrB,gBAAgB,EAAE,KAAK;YACvB,iBAAiB,EAAE,IAAI;SACxB,CAAC,CAAC;QAEH,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC5B,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,+BAA+B,EAAE,GAAG,EAAE;IAC7C,MAAM,SAAS,GAAG,CAAC,GAAW,EAAE,MAAwC,EAAE,EAAE;QAC1E,IAAI,GAAG,KAAK,qBAAqB;YAAE,OAAO,kBAAkB,CAAC;QAC7D,IAAI,GAAG,KAAK,sBAAsB;YAAE,OAAO,uBAAuB,MAAM,EAAE,QAAQ,EAAE,CAAC;QACrF,OAAO,GAAG,CAAC;IACb,CAAC,CAAC;IAEF,EAAE,CAAC,uDAAuD,EAAE,GAAG,EAAE;QAC/D,MAAM,MAAM,GAAG,6BAA6B,CAC1C;YACE,IAAI,EAAE,eAAe;YACrB,MAAM,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE;SACzB,EACD,SAAS,CACV,CAAC;QAEF,MAAM,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC;YACrB,KAAK,EAAE,kBAAkB;YACzB,IAAI,EAAE,wBAAwB;YAC9B,GAAG,EAAE,6BAA6B;SACnC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,kDAAkD,EAAE,GAAG,EAAE;QAC1D,MAAM,MAAM,GAAG,6BAA6B,CAC1C;YACE,IAAI,EAAE,SAAS;YACf,MAAM,EAAE,EAAE,QAAQ,EAAE,CAAC,EAAE;SACxB,EACD,SAAS,CACV,CAAC;QAEF,MAAM,CAAC,MAAM,CAAC,CAAC,QAAQ,EAAE,CAAC;IAC5B,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
1
+ {"version":3,"file":"desktopNotifications.test.js","sourceRoot":"","sources":["../../../../../src/tests/units/dashboard/modules/desktopNotifications.test.ts"],"names":[],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
  import { describe, expect, it } from 'vitest';
2
- import { buildSettingsViewModel, renderSettingsModalHtml, validateExternalLink, extractFormPayload, } from '../../../../dashboard/modules/settingsModal.js';
2
+ import { buildSettingsViewModel, renderSettingsModalHtml, validateExternalLink, validateQualityThreshold, extractFormPayload, } from '../../../../dashboard/modules/settingsModal.js';
3
3
  describe('settingsModal — buildSettingsViewModel', () => {
4
4
  it('populates the five editable fields plus projectName', () => {
5
5
  const viewModel = buildSettingsViewModel({
@@ -60,6 +60,7 @@ describe('settingsModal — renderSettingsModalHtml', () => {
60
60
  reviewSkill: 'review-front',
61
61
  reviewFollowupSkill: 'review-followup',
62
62
  externalLink: '',
63
+ qualityThreshold: '',
63
64
  projectName: 'A',
64
65
  });
65
66
  expect(html).toMatch(/<input[^>]+name="language"[^>]+value="fr"[^>]+checked/);
@@ -72,6 +73,7 @@ describe('settingsModal — renderSettingsModalHtml', () => {
72
73
  reviewSkill: 'review-back',
73
74
  reviewFollowupSkill: 'review-followup',
74
75
  externalLink: '',
76
+ qualityThreshold: '',
75
77
  projectName: 'A',
76
78
  });
77
79
  expect(html).toMatch(/<option[^>]+value="haiku"/);
@@ -85,6 +87,7 @@ describe('settingsModal — renderSettingsModalHtml', () => {
85
87
  reviewSkill: 'review-front',
86
88
  reviewFollowupSkill: 'review-followup',
87
89
  externalLink: 'https://notion.so/x',
90
+ qualityThreshold: '',
88
91
  projectName: 'A',
89
92
  });
90
93
  expect(html).toContain('https://notion.so/x');
@@ -97,6 +100,7 @@ describe('settingsModal — renderSettingsModalHtml', () => {
97
100
  reviewSkill: 'review-front',
98
101
  reviewFollowupSkill: 'review-followup',
99
102
  externalLink: '',
103
+ qualityThreshold: '',
100
104
  projectName: 'frontend',
101
105
  });
102
106
  expect(html).toContain('frontend');
@@ -109,6 +113,7 @@ describe('settingsModal — renderSettingsModalHtml', () => {
109
113
  reviewSkill: 'review-front',
110
114
  reviewFollowupSkill: 'review-followup',
111
115
  externalLink: '',
116
+ qualityThreshold: '',
112
117
  projectName: 'A',
113
118
  });
114
119
  expect(html).toContain('settings-modal__error');
@@ -141,13 +146,14 @@ describe('settingsModal — validateExternalLink', () => {
141
146
  });
142
147
  });
143
148
  describe('settingsModal — extractFormPayload', () => {
144
- it('returns only the 5 whitelisted keys from a FormData-like object', () => {
149
+ it('returns the six whitelisted keys (incl. qualityThreshold) from a FormData-like object', () => {
145
150
  const fakeForm = new Map([
146
151
  ['language', 'en'],
147
152
  ['defaultModel', 'sonnet'],
148
153
  ['reviewSkill', 'review-front'],
149
154
  ['reviewFollowupSkill', 'review-followup'],
150
155
  ['externalLink', 'https://notion.so/x'],
156
+ ['qualityThreshold', '7'],
151
157
  ['agents', '[{"name":"evil"}]'],
152
158
  ['retentionDays', '999'],
153
159
  ]);
@@ -156,11 +162,109 @@ describe('settingsModal — extractFormPayload', () => {
156
162
  'defaultModel',
157
163
  'externalLink',
158
164
  'language',
165
+ 'qualityThreshold',
159
166
  'reviewFollowupSkill',
160
167
  'reviewSkill',
161
168
  ]);
162
169
  expect(payload.language).toBe('en');
163
170
  expect(payload.externalLink).toBe('https://notion.so/x');
171
+ expect(payload.qualityThreshold).toBe('7');
172
+ });
173
+ });
174
+ describe('settingsModal — buildSettingsViewModel with qualityThreshold', () => {
175
+ it('exposes qualityThreshold as a string when the config has a value', () => {
176
+ const viewModel = buildSettingsViewModel({
177
+ config: {
178
+ github: false,
179
+ gitlab: true,
180
+ defaultModel: 'sonnet',
181
+ reviewSkill: 'review-front',
182
+ reviewFollowupSkill: 'review-followup',
183
+ language: 'fr',
184
+ retentionDays: 14,
185
+ qualityThreshold: 7,
186
+ },
187
+ projectName: 'A',
188
+ });
189
+ expect(viewModel.qualityThreshold).toBe('7');
190
+ });
191
+ it('exposes an empty qualityThreshold when the config has none', () => {
192
+ const viewModel = buildSettingsViewModel({
193
+ config: {
194
+ github: false,
195
+ gitlab: true,
196
+ defaultModel: 'sonnet',
197
+ reviewSkill: 'review-front',
198
+ reviewFollowupSkill: 'review-followup',
199
+ language: 'fr',
200
+ retentionDays: 14,
201
+ },
202
+ projectName: 'A',
203
+ });
204
+ expect(viewModel.qualityThreshold).toBe('');
205
+ });
206
+ });
207
+ describe('settingsModal — renderSettingsModalHtml renders qualityThreshold input', () => {
208
+ it('renders a number input for qualityThreshold pre-filled with the current value', () => {
209
+ const html = renderSettingsModalHtml({
210
+ language: 'fr',
211
+ defaultModel: 'sonnet',
212
+ reviewSkill: 'review-front',
213
+ reviewFollowupSkill: 'review-followup',
214
+ externalLink: '',
215
+ qualityThreshold: '7',
216
+ projectName: 'A',
217
+ });
218
+ expect(html).toMatch(/<input[^>]+name="qualityThreshold"[^>]+type="number"/);
219
+ expect(html).toMatch(/<input[^>]+name="qualityThreshold"[^>]+min="0"/);
220
+ expect(html).toMatch(/<input[^>]+name="qualityThreshold"[^>]+max="10"/);
221
+ expect(html).toMatch(/<input[^>]+name="qualityThreshold"[^>]+value="7"/);
222
+ });
223
+ it('renders an empty value when no threshold is set', () => {
224
+ const html = renderSettingsModalHtml({
225
+ language: 'fr',
226
+ defaultModel: 'sonnet',
227
+ reviewSkill: 'review-front',
228
+ reviewFollowupSkill: 'review-followup',
229
+ externalLink: '',
230
+ qualityThreshold: '',
231
+ projectName: 'A',
232
+ });
233
+ expect(html).toMatch(/<input[^>]+name="qualityThreshold"[^>]+value=""/);
234
+ });
235
+ });
236
+ describe('settingsModal — validateQualityThreshold', () => {
237
+ it('accepts an empty string (means clear)', () => {
238
+ expect(validateQualityThreshold('')).toEqual({ ok: true });
239
+ });
240
+ it('accepts integer values 0 through 10', () => {
241
+ for (const value of ['0', '1', '5', '7', '10']) {
242
+ expect(validateQualityThreshold(value)).toEqual({ ok: true });
243
+ }
244
+ });
245
+ it('rejects values above 10', () => {
246
+ expect(validateQualityThreshold('11')).toEqual({
247
+ ok: false,
248
+ message: 'Le seuil doit être un entier entre 0 et 10',
249
+ });
250
+ });
251
+ it('rejects negative values', () => {
252
+ expect(validateQualityThreshold('-1')).toEqual({
253
+ ok: false,
254
+ message: 'Le seuil doit être un entier entre 0 et 10',
255
+ });
256
+ });
257
+ it('rejects non-integer values', () => {
258
+ expect(validateQualityThreshold('7.5')).toEqual({
259
+ ok: false,
260
+ message: 'Le seuil doit être un entier entre 0 et 10',
261
+ });
262
+ });
263
+ it('rejects non-numeric strings', () => {
264
+ expect(validateQualityThreshold('abc')).toEqual({
265
+ ok: false,
266
+ message: 'Le seuil doit être un entier entre 0 et 10',
267
+ });
164
268
  });
165
269
  });
166
270
  //# sourceMappingURL=settingsModal.test.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"settingsModal.test.js","sourceRoot":"","sources":["../../../../../src/tests/units/dashboard/modules/settingsModal.test.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"settingsModal.test.js","sourceRoot":"","sources":["../../../../../src/tests/units/dashboard/modules/settingsModal.test.ts"],"names":[],"mappings":"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"}
@@ -45,5 +45,57 @@ describe('retrieveReviewReport use case', () => {
45
45
  expect(result.expectedPath).toContain('followup');
46
46
  }
47
47
  });
48
+ it('falls back to fallbackLocalPath when the primary location is missing', () => {
49
+ const reportGateway = new StubReviewReportGateway();
50
+ reportGateway.setReadStrategy((location) => {
51
+ if (location.localPath === '/wt/repo/frontend')
52
+ return null;
53
+ if (location.localPath === '/wt/repo') {
54
+ return {
55
+ content: '# Fallback found',
56
+ path: '/wt/repo/.claude/reviews/2026-05-22-MR-42-review.md',
57
+ };
58
+ }
59
+ return null;
60
+ });
61
+ const result = retrieveReviewReport({
62
+ session: ClaudeSessionFactory.create({ localPath: '/wt/repo/frontend' }),
63
+ today: new Date('2026-05-22T10:00:00Z'),
64
+ mergeRequestNumber: 42,
65
+ fallbackLocalPath: '/wt/repo',
66
+ }, { reportGateway });
67
+ expect(result.status).toBe('found');
68
+ if (result.status === 'found') {
69
+ expect(result.content).toBe('# Fallback found');
70
+ expect(result.path).toBe('/wt/repo/.claude/reviews/2026-05-22-MR-42-review.md');
71
+ }
72
+ });
73
+ it('does not query the fallback when the primary location resolves', () => {
74
+ const reportGateway = new StubReviewReportGateway();
75
+ reportGateway.setReadStrategy(() => ({
76
+ content: '# Primary',
77
+ path: '/wt/repo/frontend/.claude/reviews/2026-05-22-MR-42-review.md',
78
+ }));
79
+ const result = retrieveReviewReport({
80
+ session: ClaudeSessionFactory.create({ localPath: '/wt/repo/frontend' }),
81
+ today: new Date('2026-05-22T10:00:00Z'),
82
+ mergeRequestNumber: 42,
83
+ fallbackLocalPath: '/wt/repo',
84
+ }, { reportGateway });
85
+ expect(result.status).toBe('found');
86
+ expect(reportGateway.readCallCount).toBe(1);
87
+ });
88
+ it('ignores fallbackLocalPath when it equals the primary path', () => {
89
+ const reportGateway = new StubReviewReportGateway();
90
+ reportGateway.setReport(null);
91
+ const result = retrieveReviewReport({
92
+ session: ClaudeSessionFactory.create({ localPath: '/wt/repo' }),
93
+ today: new Date('2026-05-22T10:00:00Z'),
94
+ mergeRequestNumber: 42,
95
+ fallbackLocalPath: '/wt/repo',
96
+ }, { reportGateway });
97
+ expect(result.status).toBe('missing');
98
+ expect(reportGateway.readCallCount).toBe(1);
99
+ });
48
100
  });
49
101
  //# sourceMappingURL=retrieveReviewReport.usecase.test.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"retrieveReviewReport.usecase.test.js","sourceRoot":"","sources":["../../../../../../src/tests/units/modules/claude-invocation/usecases/retrieveReviewReport.usecase.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EAAE,oBAAoB,EAAE,MAAM,sEAAsE,CAAC;AAC5G,OAAO,EAAE,uBAAuB,EAAE,MAAM,oCAAoC,CAAC;AAC7E,OAAO,EAAE,oBAAoB,EAAE,MAAM,4CAA4C,CAAC;AAElF,QAAQ,CAAC,+BAA+B,EAAE,GAAG,EAAE;IAC7C,EAAE,CAAC,iDAAiD,EAAE,GAAG,EAAE;QACzD,MAAM,aAAa,GAAG,IAAI,uBAAuB,EAAE,CAAC;QACpD,aAAa,CAAC,SAAS,CAAC;YACtB,OAAO,EAAE,UAAU;YACnB,IAAI,EAAE,yDAAyD;SAChE,CAAC,CAAC;QAEH,MAAM,MAAM,GAAG,oBAAoB,CACjC;YACE,OAAO,EAAE,oBAAoB,CAAC,MAAM,EAAE;YACtC,KAAK,EAAE,IAAI,IAAI,CAAC,sBAAsB,CAAC;YACvC,kBAAkB,EAAE,EAAE;SACvB,EACD,EAAE,aAAa,EAAE,CAClB,CAAC;QAEF,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACpC,IAAI,MAAM,CAAC,MAAM,KAAK,OAAO,EAAE,CAAC;YAC9B,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QAC1C,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,kEAAkE,EAAE,GAAG,EAAE;QAC1E,MAAM,aAAa,GAAG,IAAI,uBAAuB,EAAE,CAAC;QACpD,aAAa,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;QAE9B,MAAM,MAAM,GAAG,oBAAoB,CACjC;YACE,OAAO,EAAE,oBAAoB,CAAC,MAAM,EAAE;YACtC,KAAK,EAAE,IAAI,IAAI,CAAC,sBAAsB,CAAC;YACvC,kBAAkB,EAAE,EAAE;SACvB,EACD,EAAE,aAAa,EAAE,CAClB,CAAC;QAEF,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QACtC,IAAI,MAAM,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC;YAChC,MAAM,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC,SAAS,CAAC,4BAA4B,CAAC,CAAC;QACtE,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,gDAAgD,EAAE,GAAG,EAAE;QACxD,MAAM,aAAa,GAAG,IAAI,uBAAuB,EAAE,CAAC;QACpD,aAAa,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;QAE9B,MAAM,MAAM,GAAG,oBAAoB,CACjC;YACE,OAAO,EAAE,oBAAoB,CAAC,MAAM,CAAC,EAAE,OAAO,EAAE,UAAU,EAAE,CAAC;YAC7D,KAAK,EAAE,IAAI,IAAI,CAAC,sBAAsB,CAAC;YACvC,kBAAkB,EAAE,EAAE;SACvB,EACD,EAAE,aAAa,EAAE,CAClB,CAAC;QAEF,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QACtC,IAAI,MAAM,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC;YAChC,MAAM,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC,SAAS,CAAC,UAAU,CAAC,CAAC;QACpD,CAAC;IACH,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
1
+ {"version":3,"file":"retrieveReviewReport.usecase.test.js","sourceRoot":"","sources":["../../../../../../src/tests/units/modules/claude-invocation/usecases/retrieveReviewReport.usecase.test.ts"],"names":[],"mappings":"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"}
@@ -237,5 +237,44 @@ describe('projectConfigRoutes — PATCH /api/project-config', () => {
237
237
  expect(response.json().error).toBe('Échec de la sauvegarde');
238
238
  await app.close();
239
239
  });
240
+ it('accepts a numeric qualityThreshold in the patch body', async () => {
241
+ const gateway = new StubProjectConfigGateway();
242
+ gateway.set('/repo/A', baseConfig());
243
+ const app = await buildAppWithPatch(gateway);
244
+ const response = await app.inject({
245
+ method: 'PATCH',
246
+ url: '/api/project-config?path=' + encodeURIComponent('/repo/A'),
247
+ payload: { qualityThreshold: 7 },
248
+ });
249
+ expect(response.statusCode).toBe(200);
250
+ expect(response.json().config.qualityThreshold).toBe(7);
251
+ await app.close();
252
+ });
253
+ it('returns 400 when qualityThreshold is out of range', async () => {
254
+ const gateway = new StubProjectConfigGateway();
255
+ gateway.set('/repo/A', baseConfig());
256
+ const app = await buildAppWithPatch(gateway);
257
+ const response = await app.inject({
258
+ method: 'PATCH',
259
+ url: '/api/project-config?path=' + encodeURIComponent('/repo/A'),
260
+ payload: { qualityThreshold: 15 },
261
+ });
262
+ expect(response.statusCode).toBe(400);
263
+ expect(response.json().error).toMatch(/qualityThreshold/);
264
+ await app.close();
265
+ });
266
+ it('clears qualityThreshold from the persisted config when patch sends null', async () => {
267
+ const gateway = new StubProjectConfigGateway();
268
+ gateway.set('/repo/A', baseConfig({ qualityThreshold: 7 }));
269
+ const app = await buildAppWithPatch(gateway);
270
+ const response = await app.inject({
271
+ method: 'PATCH',
272
+ url: '/api/project-config?path=' + encodeURIComponent('/repo/A'),
273
+ payload: { qualityThreshold: null },
274
+ });
275
+ expect(response.statusCode).toBe(200);
276
+ expect(response.json().config.qualityThreshold).toBeUndefined();
277
+ await app.close();
278
+ });
240
279
  });
241
280
  //# sourceMappingURL=projectConfig.routes.test.js.map