@cat-factory/app 0.270.3 → 0.272.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.
@@ -2,7 +2,7 @@ import { describe, it, expect, beforeEach, vi } from 'vitest'
2
2
  import { useBugHuntStore } from '~/stores/bugHunt'
3
3
  import { useWorkspaceStore } from '~/stores/workspace'
4
4
  import { ApiError } from '~/composables/api/errors'
5
- import type { TaskSourceKind, TrackerBoardsView } from '~/types/domain'
5
+ import type { BugHuntResult, TaskSourceKind, TrackerBoardsView } from '~/types/domain'
6
6
 
7
7
  // What the board picker does with a FAILED board read. Only one failure means "this tracker
8
8
  // cannot enumerate boards, so type one in"; every other failure has to stay visible as an error,
@@ -23,20 +23,43 @@ const boardsUnsupported = () =>
23
23
  function stubApi(): {
24
24
  store: ReturnType<typeof useBugHuntStore>
25
25
  serve: (fn: () => Promise<unknown>) => void
26
+ serveHunt: (fn: () => Promise<unknown>) => void
26
27
  } {
27
28
  let handler: () => Promise<unknown> = () => Promise.resolve({ source: 'jira', boards: [] })
29
+ let huntHandler: () => Promise<unknown> = () => Promise.resolve(huntResult())
28
30
  vi.stubGlobal('useApi', () => ({
29
31
  listTrackerBoards: (_ws: string, _source: TaskSourceKind) =>
30
32
  handler() as Promise<TrackerBoardsView>,
33
+ runBugHunt: (_ws: string, _source: TaskSourceKind, _input: unknown) =>
34
+ huntHandler() as Promise<BugHuntResult>,
31
35
  }))
32
36
  return {
33
37
  store: useBugHuntStore(),
34
38
  serve: (fn) => {
35
39
  handler = fn
36
40
  },
41
+ serveHunt: (fn) => {
42
+ huntHandler = fn
43
+ },
37
44
  }
38
45
  }
39
46
 
47
+ /** An empty but well-formed scan result, so a success case asserts on the store, not the shape. */
48
+ function huntResult(): BugHuntResult {
49
+ return {
50
+ source: 'github',
51
+ board: 'acme/web',
52
+ analysisStatus: 'empty',
53
+ model: null,
54
+ candidates: [],
55
+ scanned: 0,
56
+ truncated: false,
57
+ }
58
+ }
59
+
60
+ /** The one scan input shape the modal builds; the store only passes it through. */
61
+ const SCAN = { containerId: 'blk_auth', board: null }
62
+
40
63
  describe('bug hunt store — board listing failures', () => {
41
64
  beforeEach(() => {
42
65
  useWorkspaceStore().workspaceId = 'ws1'
@@ -79,6 +102,22 @@ describe('bug hunt store — board listing failures', () => {
79
102
  expect(store.boards.map((b) => b.id)).toEqual(['t1'])
80
103
  })
81
104
 
105
+ it('drops the previous tracker failure when the next one has no board to list', async () => {
106
+ // A repo-backed tracker renders no board field at all, so a stale "boards could not be
107
+ // loaded" warning would sit under a control that is not there, blaming this tracker for the
108
+ // last one's outage.
109
+ const { store, serve } = stubApi()
110
+ serve(() => Promise.reject(apiError(502, 'upstream')))
111
+ await store.loadBoards('jira')
112
+
113
+ store.dropBoards('github')
114
+
115
+ expect(store.boardsSource).toBe('github')
116
+ expect(store.boards).toEqual([])
117
+ expect(store.boardsError).toBeNull()
118
+ expect(store.boardsErrorReason).toBeNull()
119
+ })
120
+
82
121
  it('a source switch mid-flight never lands the older tracker failure on the newer one', async () => {
83
122
  const { store, serve } = stubApi()
84
123
  let rejectJira!: (e: unknown) => void
@@ -100,4 +139,86 @@ describe('bug hunt store — board listing failures', () => {
100
139
  expect(store.boardsErrorReason).toBeNull()
101
140
  expect(store.boardsError).toBeNull()
102
141
  })
142
+
143
+ it('leaves nothing loading when the next tracker has no board to list', async () => {
144
+ // The abandoned listing's own `finally` will not run until it settles, which for a hanging
145
+ // tracker is never. A picker (or a Hunt button) gated on the flag would wait on a request
146
+ // nobody is waiting for.
147
+ const { store, serve } = stubApi()
148
+ serve(() => new Promise(() => {}))
149
+ void store.loadBoards('jira')
150
+ expect(store.boardsLoading).toBe(true)
151
+
152
+ store.dropBoards('github')
153
+
154
+ expect(store.boardsLoading).toBe(false)
155
+ })
156
+
157
+ it('a superseded listing never reports the tracker now loading as done', async () => {
158
+ const { store, serve } = stubApi()
159
+ let settleJira!: (v: unknown) => void
160
+ serve(() => new Promise((res) => (settleJira = res)))
161
+ const inFlight = store.loadBoards('jira')
162
+
163
+ serve(() => new Promise(() => {}))
164
+ void store.loadBoards('linear')
165
+ settleJira({ source: 'jira', boards: [] })
166
+ await inFlight
167
+
168
+ expect(store.boardsSource).toBe('linear')
169
+ expect(store.boardsLoading).toBe(true)
170
+ })
171
+ })
172
+
173
+ describe('bug hunt store — scan failures', () => {
174
+ beforeEach(() => {
175
+ useWorkspaceStore().workspaceId = 'ws1'
176
+ })
177
+
178
+ it('keeps the backend reason for the one failure the surface words itself', async () => {
179
+ // `repo_not_linked` names something fixable on this board, so the modal states it beside the
180
+ // scope it invalidates instead of raising a toast. That routing reads ONLY this field.
181
+ const { store, serveHunt } = stubApi()
182
+ serveHunt(() => Promise.reject(apiError(422, 'validation', { reason: 'repo_not_linked' })))
183
+
184
+ expect(await store.hunt('github', SCAN)).toBe(false)
185
+
186
+ expect(store.huntErrorReason).toBe('repo_not_linked')
187
+ expect(store.huntError).toBeTruthy()
188
+ expect(store.result).toBeNull()
189
+ })
190
+
191
+ it('records NO reason for a scan that simply failed, so it stays a toast', async () => {
192
+ const { store, serveHunt } = stubApi()
193
+ serveHunt(() => Promise.reject(apiError(502, 'upstream')))
194
+
195
+ expect(await store.hunt('jira', { containerId: 'blk_auth', board: 'PROJ' })).toBe(false)
196
+
197
+ expect(store.huntErrorReason).toBeNull()
198
+ expect(store.huntError).toBeTruthy()
199
+ })
200
+
201
+ it('clears a previous scan failure once a later scan succeeds', async () => {
202
+ const { store, serveHunt } = stubApi()
203
+ serveHunt(() => Promise.reject(apiError(422, 'validation', { reason: 'repo_not_linked' })))
204
+ await store.hunt('github', SCAN)
205
+
206
+ serveHunt(() => Promise.resolve(huntResult()))
207
+ expect(await store.hunt('github', SCAN)).toBe(true)
208
+
209
+ expect(store.huntError).toBeNull()
210
+ expect(store.huntErrorReason).toBeNull()
211
+ expect(store.result?.board).toBe('acme/web')
212
+ })
213
+
214
+ it('drops the reason on reset, so a re-opened hunt never re-states the old refusal', async () => {
215
+ const { store, serveHunt } = stubApi()
216
+ serveHunt(() => Promise.reject(apiError(422, 'validation', { reason: 'repo_not_linked' })))
217
+ await store.hunt('github', SCAN)
218
+
219
+ store.reset()
220
+
221
+ expect(store.huntErrorReason).toBeNull()
222
+ expect(store.huntError).toBeNull()
223
+ })
103
224
  })
@@ -38,6 +38,13 @@ export const useBugHuntStore = defineStore('bugHunt', () => {
38
38
  const result = ref<BugHuntResult | null>(null)
39
39
  const hunting = ref(false)
40
40
  const huntError = ref<string | null>(null)
41
+ /**
42
+ * The backend's machine-readable reason for a failed scan, kept for the same reason
43
+ * {@link boardsErrorReason} is: `repo_not_linked` says the service this hunt is scoped to has
44
+ * no repository to read issues from, which is a state the person can fix on the board and the
45
+ * only one the surface words itself. Every other failure stays a toast.
46
+ */
47
+ const huntErrorReason = ref<string | null>(null)
41
48
  /** The candidate currently being adopted, so only its own row shows a spinner. */
42
49
  const adopting = ref<string | null>(null)
43
50
 
@@ -62,20 +69,44 @@ export const useBugHuntStore = defineStore('bugHunt', () => {
62
69
  boardsError.value = e instanceof Error ? e.message : String(e)
63
70
  boardsErrorReason.value = apiErrorReason(e)
64
71
  } finally {
65
- boardsLoading.value = false
72
+ // Only the listing the picker is still showing owns the flag. A superseded one clearing it
73
+ // unconditionally would report the tracker now being loaded as done, which is the same
74
+ // mistake in the opposite direction from the one `dropBoards` avoids.
75
+ if (boardsSource.value === source) boardsLoading.value = false
66
76
  }
67
77
  }
68
78
 
79
+ /**
80
+ * Forget the board listing, because the tracker now in the picker has none to offer: its board
81
+ * is the chosen service's repository, resolved server-side. Called INSTEAD of `loadBoards`, so a
82
+ * previous tracker's list (or its failure, which the surface renders as a warning) cannot sit
83
+ * under a tracker that has no board field at all. `boardsSource` moves with it, so a listing
84
+ * still in flight for that previous tracker lands on nothing rather than reviving the picker.
85
+ */
86
+ function dropBoards(source: TaskSourceKind): void {
87
+ boardsSource.value = source
88
+ boards.value = []
89
+ boardsError.value = null
90
+ boardsErrorReason.value = null
91
+ // The listing in flight belongs to the tracker being left, and its `finally` will not run
92
+ // until it settles — indefinitely, if that tracker hangs. Clearing the flag with the rest of
93
+ // the state is what makes "land on nothing" true of the WHOLE listing rather than of four of
94
+ // its five fields, so a reader gating on it cannot wait on a request nobody is waiting for.
95
+ boardsLoading.value = false
96
+ }
97
+
69
98
  /** Run a hunt and keep its ranked result. Returns false when the scan itself failed. */
70
99
  async function hunt(source: TaskSourceKind, input: RunBugHuntInput): Promise<boolean> {
71
100
  hunting.value = true
72
101
  huntError.value = null
102
+ huntErrorReason.value = null
73
103
  try {
74
104
  result.value = await api.runBugHunt(workspace.requireId(), source, input)
75
105
  return true
76
106
  } catch (e) {
77
107
  result.value = null
78
108
  huntError.value = e instanceof Error ? e.message : String(e)
109
+ huntErrorReason.value = apiErrorReason(e)
79
110
  return false
80
111
  } finally {
81
112
  hunting.value = false
@@ -117,6 +148,7 @@ export const useBugHuntStore = defineStore('bugHunt', () => {
117
148
  function reset(): void {
118
149
  result.value = null
119
150
  huntError.value = null
151
+ huntErrorReason.value = null
120
152
  adopting.value = null
121
153
  }
122
154
 
@@ -131,8 +163,10 @@ export const useBugHuntStore = defineStore('bugHunt', () => {
131
163
  hasResult,
132
164
  hunting,
133
165
  huntError,
166
+ huntErrorReason,
134
167
  adopting,
135
168
  loadBoards,
169
+ dropBoards,
136
170
  hunt,
137
171
  adopt,
138
172
  reset,
@@ -34,6 +34,8 @@ const jiraDescriptor: TaskSourceState = {
34
34
  enabled: true,
35
35
  supportsIntake: true,
36
36
  ignoredIntakePredicates: [],
37
+ // Jira issues belong to a project, not a repository, so a hunt on it picks a board.
38
+ repoBacked: false,
37
39
  // Jira carries its own credentials, so it rides no VCS connection.
38
40
  ridesVcsProvider: null,
39
41
  }
@@ -14,6 +14,7 @@ function state(ignored: TaskSourceState['ignoredIntakePredicates']): TaskSourceS
14
14
  enabled: true,
15
15
  ridesVcsProvider: 'gitlab',
16
16
  supportsIntake: true,
17
+ repoBacked: true,
17
18
  ignoredIntakePredicates: ignored,
18
19
  }
19
20
  }
@@ -2033,6 +2033,13 @@
2033
2033
  "awaitingChoice": "Wartet auf eine menschliche Wahl",
2034
2034
  "approvalGate": "Freigabe-Gate",
2035
2035
  "companionReview": "Begleiter-Prüfung",
2036
+ "findingSeverity": {
2037
+ "blocker": "Muss behoben werden",
2038
+ "major": "Sollte behoben werden",
2039
+ "minor": "Geringfügig",
2040
+ "ungraded": "Kommentar",
2041
+ "unrecognized": "Unbekannte Stufe ({level})"
2042
+ },
2036
2043
  "correctionIterations": "{count} Korrekturiteration. | {count} Korrekturiterationen.",
2037
2044
  "state": {
2038
2045
  "pending": "Ausstehend",
@@ -2069,7 +2076,9 @@
2069
2076
  "closeEsc": "Schließen (Esc)",
2070
2077
  "companionCapHeading": "{agent} hat sein Überarbeitungslimit von {attempts} Versuchen erreicht, noch unter der {threshold}-Schwelle.",
2071
2078
  "companionCapDetail": "Führe eine weitere automatische Überarbeitungsrunde durch, fahre mit dem nächsten Schritt fort und akzeptiere die aktuelle Ausgabe, oder stoppe und setze die Aufgabe zurück, damit du die Eingaben bearbeiten und erneut einreichen kannst.",
2072
- "companionStalledHeading": "{agent} hat die Überarbeitungsschleife nach {attempts} von {maxAttempts} Runden gestoppt: Die letzte Überarbeitung kam unverändert zurück und die Bewertung blieb unter der {threshold}-Schwelle.",
2079
+ "companionCapBlockedHeading": "{agent} hat sein Überarbeitungslimit von {attempts} Versuchen erreicht, {count} zwingender Befund ist weiterhin offen. | {agent} hat sein Überarbeitungslimit von {attempts} Versuchen erreicht, {count} zwingende Befunde sind weiterhin offen.",
2080
+ "companionCapBlockedDetail": "Fortfahren akzeptiert die Arbeit mit diesen ungelösten Befunden, deshalb trifft die Risikorichtlinie dieses Laufs diese Wahl nie für dich. Führe eine weitere automatische Überarbeitungsrunde durch, fahre trotzdem mit der aktuellen Ausgabe fort, oder stoppe und setze die Aufgabe zurück, damit du die Eingaben bearbeiten und erneut einreichen kannst.",
2081
+ "companionStalledHeading": "{agent} hat die Überarbeitungsschleife nach {attempts} von {maxAttempts} Runden gestoppt: Die letzte Überarbeitung kam unverändert zurück und die Bewertung hat sich nicht verändert.",
2073
2082
  "companionStalledDetail": "Die restlichen Runden bleiben ungenutzt, weil sie nur ein bereits abgegebenes Urteil wiederholen würden. Führe trotzdem eine weitere Runde durch, fahre mit dem nächsten Schritt fort und akzeptiere die aktuelle Ausgabe, oder stoppe und setze die Aufgabe zurück, damit du die Eingaben bearbeiten und erneut einreichen kannst.",
2074
2083
  "infraAttempts": "Infrastruktur-Versuche",
2075
2084
  "hideInfraAttempts": "Infrastruktur-Versuche ausblenden",
@@ -4287,7 +4296,9 @@
4287
4296
  "tracker": "Tracker",
4288
4297
  "board": "Board",
4289
4298
  "pickBoard": "Board auswählen",
4290
- "boardPlaceholder": "Projektschlüssel, Team-ID oder owner/repo",
4299
+ "boardPlaceholder": "Projektschlüssel oder Team-ID",
4300
+ "boardFromService": "Das mit diesem Service verknüpfte Repository",
4301
+ "boardNeedsRepo": "Mit diesem Service ist kein Repository verknüpft, es gibt also keine Tickets zu durchsuchen. Verknüpfe eines im Service-Panel.",
4291
4302
  "boardsFailed": "Boards konnten nicht geladen werden: {reason}",
4292
4303
  "issueType": "Vorgangstyp",
4293
4304
  "issueTypeHelp": "Standard ist bug. Wird von Trackern ohne Vorgangstypen ignoriert.",
@@ -4296,6 +4307,8 @@
4296
4307
  "labelsHelp": "Durch Komma getrennt. Alle müssen vorhanden sein.",
4297
4308
  "adoptInto": "Ausgewählten Fehler hinzufügen zu",
4298
4309
  "adoptingInto": "Der ausgewählte Fehler landet in {container}",
4310
+ "huntIn": "Zu durchsuchender Service",
4311
+ "huntingIn": "Durchsucht wird das mit {container} verknüpfte Repository; dort landet auch der ausgewählte Fehler.",
4299
4312
  "run": "Jagen",
4300
4313
  "running": "Board wird gelesen und die Funde werden bewertet…",
4301
4314
  "huntFailed": "Die Jagd ist fehlgeschlagen",
@@ -4308,6 +4321,7 @@
4308
4321
  "noCandidates": "Auf diesem Board gab es keine offenen, nicht zugewiesenen Fehler.",
4309
4322
  "ratings": "Auswirkung {impact}/5, Aufwand {complexity}/5, Konfidenz {confidence}",
4310
4323
  "viaModel": "Bewertet von {model}.",
4324
+ "scannedBoard": "{board} durchsucht.",
4311
4325
  "truncated": "Es wurden nur die ersten {count} passenden Fehler geprüft; dieses Board enthält mehr.",
4312
4326
  "comments": "{count} Kommentar | {count} Kommentare",
4313
4327
  "confidence": {
@@ -4321,6 +4335,10 @@
4321
4335
  "failed": "Die Bewertung konnte nicht abgeschlossen werden, daher sind diese unbewertet.",
4322
4336
  "over_budget": "Dieser Arbeitsbereich hat sein Ausgabenbudget überschritten, daher wurden diese nicht bewertet.",
4323
4337
  "empty": "Nichts zu bewerten."
4338
+ },
4339
+ "refusal": {
4340
+ "boardFromService": "Dieser Tracker durchsucht das Repository, mit dem der gewählte Service verknüpft ist, und hat daher kein eigenes Board.",
4341
+ "missingBoard": "Wähle das Board aus, auf dem gesucht werden soll."
4324
4342
  }
4325
4343
  },
4326
4344
  "pipeline": {
@@ -1458,6 +1458,13 @@
1458
1458
  "awaitingChoice": "Awaiting a human choice",
1459
1459
  "approvalGate": "Approval gate",
1460
1460
  "companionReview": "Companion review",
1461
+ "findingSeverity": {
1462
+ "blocker": "Must fix",
1463
+ "major": "Should fix",
1464
+ "minor": "Minor",
1465
+ "ungraded": "Comment",
1466
+ "unrecognized": "Level not recognised ({level})"
1467
+ },
1461
1468
  "correctionIterations": "{count} correction iteration. | {count} correction iterations.",
1462
1469
  "state": {
1463
1470
  "pending": "Pending",
@@ -1494,7 +1501,9 @@
1494
1501
  "closeEsc": "Close (Esc)",
1495
1502
  "companionCapHeading": "{agent} hit its {attempts}-attempt rework limit, still below the {threshold} bar.",
1496
1503
  "companionCapDetail": "Do one more automatic rework round, proceed to the next step accepting the current output, or stop and reset the task so you can edit the inputs and resubmit.",
1497
- "companionStalledHeading": "{agent} stopped the rework loop after {attempts} of {maxAttempts} rounds: the last revision came back unchanged and the rating held below the {threshold} bar.",
1504
+ "companionCapBlockedHeading": "{agent} hit its {attempts}-attempt rework limit with {count} must-fix finding still open. | {agent} hit its {attempts}-attempt rework limit with {count} must-fix findings still open.",
1505
+ "companionCapBlockedDetail": "Proceeding accepts the work with those findings unfixed, which is why this run’s risk policy will never take that choice for you. Do one more automatic rework round, proceed anyway on the current output, or stop and reset the task so you can edit the inputs and resubmit.",
1506
+ "companionStalledHeading": "{agent} stopped the rework loop after {attempts} of {maxAttempts} rounds: the last revision came back unchanged and its rating did not move.",
1498
1507
  "companionStalledDetail": "The remaining rounds were left unspent because they would only repeat a verdict already given. Do one more round anyway, proceed to the next step accepting the current output, or stop and reset the task so you can edit the inputs and resubmit.",
1499
1508
  "infraAttempts": "Infrastructure attempts",
1500
1509
  "hideInfraAttempts": "Hide infrastructure attempts",
@@ -4888,7 +4897,9 @@
4888
4897
  "tracker": "Tracker",
4889
4898
  "board": "Board",
4890
4899
  "pickBoard": "Pick a board",
4891
- "boardPlaceholder": "Project key, team id or owner/repo",
4900
+ "boardPlaceholder": "Project key or team id",
4901
+ "boardFromService": "The repository this service is linked to",
4902
+ "boardNeedsRepo": "This service has no repository linked, so it has no issues to hunt. Link one from the service panel.",
4892
4903
  "boardsFailed": "Boards could not be loaded: {reason}",
4893
4904
  "issueType": "Issue type",
4894
4905
  "issueTypeHelp": "Defaults to bug. Ignored by trackers with no issue types.",
@@ -4897,6 +4908,8 @@
4897
4908
  "labelsHelp": "Comma separated. All of them must be present.",
4898
4909
  "adoptInto": "Add the picked bug to",
4899
4910
  "adoptingInto": "The picked bug lands in {container}",
4911
+ "huntIn": "Service to hunt",
4912
+ "huntingIn": "Hunting the repository linked to {container}; the picked bug lands there too.",
4900
4913
  "run": "Hunt",
4901
4914
  "running": "Reading the board and rating what it finds…",
4902
4915
  "huntFailed": "The hunt failed",
@@ -4909,6 +4922,7 @@
4909
4922
  "noCandidates": "No open, unassigned bugs matched on this board.",
4910
4923
  "ratings": "Impact {impact}/5, complexity {complexity}/5, {confidence} confidence",
4911
4924
  "viaModel": "Rated by {model}.",
4925
+ "scannedBoard": "Scanned {board}.",
4912
4926
  "truncated": "Only the first {count} matching bugs were scanned; this board holds more.",
4913
4927
  "comments": "{count} comment | {count} comments",
4914
4928
  "@comments": {
@@ -4925,6 +4939,10 @@
4925
4939
  "failed": "The rating could not be completed, so these are unrated.",
4926
4940
  "over_budget": "This workspace is over its spend budget, so these were not rated.",
4927
4941
  "empty": "Nothing to rate."
4942
+ },
4943
+ "refusal": {
4944
+ "boardFromService": "This tracker scans the repository the chosen service is linked to, so it has no board of its own.",
4945
+ "missingBoard": "Pick the board to hunt on."
4928
4946
  }
4929
4947
  },
4930
4948
  "pipeline": {
@@ -1348,6 +1348,13 @@
1348
1348
  "awaitingChoice": "Esperando una elección humana",
1349
1349
  "approvalGate": "Verificación de aprobación",
1350
1350
  "companionReview": "Revisión del acompañante",
1351
+ "findingSeverity": {
1352
+ "blocker": "Debe corregirse",
1353
+ "major": "Debería corregirse",
1354
+ "minor": "Menor",
1355
+ "ungraded": "Comentario",
1356
+ "unrecognized": "Nivel no reconocido ({level})"
1357
+ },
1351
1358
  "correctionIterations": "{count} iteración de corrección. | {count} iteraciones de corrección.",
1352
1359
  "state": {
1353
1360
  "pending": "Pendiente",
@@ -1403,7 +1410,9 @@
1403
1410
  "closeEsc": "Cerrar (Esc)",
1404
1411
  "companionCapHeading": "{agent} alcanzó su límite de {attempts} intentos de reelaboración, aún por debajo del umbral de {threshold}.",
1405
1412
  "companionCapDetail": "Haz una ronda más de reelaboración automática, avanza al siguiente paso aceptando la salida actual, o detén y restablece la tarea para que puedas editar las entradas y reenviarla.",
1406
- "companionStalledHeading": "{agent} detuvo el bucle de reelaboración tras {attempts} de {maxAttempts} rondas: la última revisión volvió sin cambios y la puntuación se mantuvo por debajo del umbral de {threshold}.",
1413
+ "companionCapBlockedHeading": "{agent} alcanzó su límite de {attempts} intentos de reelaboración con {count} hallazgo obligatorio aún sin resolver. | {agent} alcanzó su límite de {attempts} intentos de reelaboración con {count} hallazgos obligatorios aún sin resolver.",
1414
+ "companionCapBlockedDetail": "Continuar acepta el trabajo con esos hallazgos sin resolver, por eso la política de riesgo de esta ejecución nunca tomará esa decisión por ti. Haz una ronda más de reelaboración automática, continúa igualmente con la salida actual, o detén y restablece la tarea para que puedas editar las entradas y reenviarla.",
1415
+ "companionStalledHeading": "{agent} detuvo el bucle de reelaboración tras {attempts} de {maxAttempts} rondas: la última revisión volvió sin cambios y la puntuación no se movió.",
1407
1416
  "companionStalledDetail": "Las rondas restantes quedaron sin usar porque solo repetirían un veredicto ya emitido. Haz una ronda más de todos modos, avanza al siguiente paso aceptando la salida actual, o detén y restablece la tarea para que puedas editar las entradas y reenviarla.",
1408
1417
  "infraAttempts": "Intentos de infraestructura",
1409
1418
  "hideInfraAttempts": "Ocultar intentos de infraestructura",
@@ -4730,7 +4739,9 @@
4730
4739
  "tracker": "Gestor de incidencias",
4731
4740
  "board": "Tablero",
4732
4741
  "pickBoard": "Elige un tablero",
4733
- "boardPlaceholder": "Clave de proyecto, id de equipo o owner/repo",
4742
+ "boardPlaceholder": "Clave de proyecto o id de equipo",
4743
+ "boardFromService": "El repositorio vinculado a este servicio",
4744
+ "boardNeedsRepo": "Este servicio no tiene ningún repositorio vinculado, así que no hay incidencias que explorar. Vincula uno desde el panel del servicio.",
4734
4745
  "boardsFailed": "No se pudieron cargar los tableros: {reason}",
4735
4746
  "issueType": "Tipo de incidencia",
4736
4747
  "issueTypeHelp": "Por defecto bug. Se ignora en gestores sin tipos de incidencia.",
@@ -4739,6 +4750,8 @@
4739
4750
  "labelsHelp": "Separadas por comas. Todas deben estar presentes.",
4740
4751
  "adoptInto": "Añadir el error elegido a",
4741
4752
  "adoptingInto": "El error elegido se añade a {container}",
4753
+ "huntIn": "Servicio a explorar",
4754
+ "huntingIn": "Se explora el repositorio vinculado a {container}; el error elegido también se añade ahí.",
4742
4755
  "run": "Cazar",
4743
4756
  "running": "Leyendo el tablero y valorando lo que encuentra…",
4744
4757
  "huntFailed": "La caza ha fallado",
@@ -4751,6 +4764,7 @@
4751
4764
  "noCandidates": "No hay errores abiertos y sin asignar que coincidan en este tablero.",
4752
4765
  "ratings": "Impacto {impact}/5, complejidad {complexity}/5, confianza {confidence}",
4753
4766
  "viaModel": "Valorado por {model}.",
4767
+ "scannedBoard": "Explorado {board}.",
4754
4768
  "truncated": "Solo se han explorado los primeros {count} errores coincidentes; este tablero contiene más.",
4755
4769
  "comments": "{count} comentario | {count} comentarios",
4756
4770
  "confidence": {
@@ -4764,6 +4778,10 @@
4764
4778
  "failed": "No se ha podido completar la valoración, así que están sin valorar.",
4765
4779
  "over_budget": "Este espacio de trabajo ha superado su presupuesto de gasto, así que no se han valorado.",
4766
4780
  "empty": "Nada que valorar."
4781
+ },
4782
+ "refusal": {
4783
+ "boardFromService": "Este rastreador analiza el repositorio al que está vinculado el servicio elegido, así que no tiene un tablero propio.",
4784
+ "missingBoard": "Elige el tablero en el que buscar."
4767
4785
  }
4768
4786
  },
4769
4787
  "pipeline": {
@@ -1348,6 +1348,13 @@
1348
1348
  "awaitingChoice": "En attente d'un choix humain",
1349
1349
  "approvalGate": "Gate d'approbation",
1350
1350
  "companionReview": "Revue du compagnon",
1351
+ "findingSeverity": {
1352
+ "blocker": "À corriger absolument",
1353
+ "major": "À corriger",
1354
+ "minor": "Mineur",
1355
+ "ungraded": "Commentaire",
1356
+ "unrecognized": "Niveau non reconnu ({level})"
1357
+ },
1351
1358
  "correctionIterations": "{count} itération de correction. | {count} itérations de correction.",
1352
1359
  "state": {
1353
1360
  "pending": "En attente",
@@ -1403,7 +1410,9 @@
1403
1410
  "closeEsc": "Fermer (Esc)",
1404
1411
  "companionCapHeading": "{agent} a atteint sa limite de {attempts} tentatives de retravail, toujours en dessous du seuil {threshold}.",
1405
1412
  "companionCapDetail": "Effectuer un tour de retravail automatique supplémentaire, passer à l'étape suivante en acceptant la sortie actuelle, ou arrêter et réinitialiser la tâche pour modifier les entrées et la resoumettre.",
1406
- "companionStalledHeading": "{agent} a arrêté la boucle de retravail après {attempts} tours sur {maxAttempts} : la dernière révision est revenue inchangée et la note est restée sous le seuil {threshold}.",
1413
+ "companionCapBlockedHeading": "{agent} a atteint sa limite de {attempts} tentatives de retravail avec {count} point bloquant encore ouvert. | {agent} a atteint sa limite de {attempts} tentatives de retravail avec {count} points bloquants encore ouverts.",
1414
+ "companionCapBlockedDetail": "Poursuivre revient à accepter le travail avec ces points non corrigés, c’est pourquoi la politique de risque de cette exécution ne prendra jamais cette décision à votre place. Effectuer un tour de retravail automatique supplémentaire, poursuivre malgré tout avec la sortie actuelle, ou arrêter et réinitialiser la tâche pour modifier les entrées et la resoumettre.",
1415
+ "companionStalledHeading": "{agent} a arrêté la boucle de retravail après {attempts} tours sur {maxAttempts} : la dernière révision est revenue inchangée et la note n'a pas bougé.",
1407
1416
  "companionStalledDetail": "Les tours restants n'ont pas été utilisés car ils ne feraient que répéter un verdict déjà rendu. Effectuer tout de même un tour supplémentaire, passer à l'étape suivante en acceptant la sortie actuelle, ou arrêter et réinitialiser la tâche pour modifier les entrées et la resoumettre.",
1408
1417
  "infraAttempts": "Tentatives d'infrastructure",
1409
1418
  "hideInfraAttempts": "Masquer les tentatives d'infrastructure",
@@ -4730,7 +4739,9 @@
4730
4739
  "tracker": "Gestionnaire de tickets",
4731
4740
  "board": "Tableau",
4732
4741
  "pickBoard": "Choisir un tableau",
4733
- "boardPlaceholder": "Clé de projet, id d'équipe ou owner/repo",
4742
+ "boardPlaceholder": "Clé de projet ou id d'équipe",
4743
+ "boardFromService": "Le dépôt lié à ce service",
4744
+ "boardNeedsRepo": "Aucun dépôt n'est lié à ce service : il n'y a donc pas de tickets à explorer. Liez-en un depuis le panneau du service.",
4734
4745
  "boardsFailed": "Impossible de charger les tableaux : {reason}",
4735
4746
  "issueType": "Type de ticket",
4736
4747
  "issueTypeHelp": "Par défaut bug. Ignoré par les gestionnaires sans types de tickets.",
@@ -4739,6 +4750,8 @@
4739
4750
  "labelsHelp": "Séparées par des virgules. Toutes doivent être présentes.",
4740
4751
  "adoptInto": "Ajouter le bug retenu à",
4741
4752
  "adoptingInto": "Le bug retenu est ajouté à {container}",
4753
+ "huntIn": "Service à explorer",
4754
+ "huntingIn": "Exploration du dépôt lié à {container} ; le bug retenu y est ajouté.",
4742
4755
  "run": "Chasser",
4743
4756
  "running": "Lecture du tableau et évaluation des résultats…",
4744
4757
  "huntFailed": "La chasse a échoué",
@@ -4751,6 +4764,7 @@
4751
4764
  "noCandidates": "Aucun bug ouvert et non assigné ne correspond sur ce tableau.",
4752
4765
  "ratings": "Impact {impact}/5, complexité {complexity}/5, confiance {confidence}",
4753
4766
  "viaModel": "Évalué par {model}.",
4767
+ "scannedBoard": "{board} exploré.",
4754
4768
  "truncated": "Seuls les {count} premiers bugs correspondants ont été parcourus ; ce tableau en contient davantage.",
4755
4769
  "comments": "{count} commentaire | {count} commentaires",
4756
4770
  "confidence": {
@@ -4764,6 +4778,10 @@
4764
4778
  "failed": "L'évaluation n'a pas pu aboutir, ils sont donc non évalués.",
4765
4779
  "over_budget": "Cet espace de travail a dépassé son budget de dépenses, ces bogues n'ont donc pas été évalués.",
4766
4780
  "empty": "Rien à évaluer."
4781
+ },
4782
+ "refusal": {
4783
+ "boardFromService": "Ce traqueur analyse le dépôt auquel le service choisi est lié : il n'a donc pas de tableau propre.",
4784
+ "missingBoard": "Choisissez le tableau sur lequel chercher."
4767
4785
  }
4768
4786
  },
4769
4787
  "pipeline": {
@@ -1367,6 +1367,13 @@
1367
1367
  "awaitingChoice": "ממתין לבחירה אנושית",
1368
1368
  "approvalGate": "שער אישור",
1369
1369
  "companionReview": "סקירת מלווה",
1370
+ "findingSeverity": {
1371
+ "blocker": "חובה לתקן",
1372
+ "major": "כדאי לתקן",
1373
+ "minor": "מינורי",
1374
+ "ungraded": "הערה",
1375
+ "unrecognized": "רמה לא מזוהה ({level})"
1376
+ },
1370
1377
  "correctionIterations": "מחזור תיקון אחד. | שני מחזורי תיקון. | {count} מחזורי תיקון.",
1371
1378
  "state": {
1372
1379
  "pending": "ממתין",
@@ -1403,7 +1410,9 @@
1403
1410
  "closeEsc": "סגור (Esc)",
1404
1411
  "companionCapHeading": "{agent} הגיע למגבלת {attempts} ניסיונות עיבוד מחדש, ועדיין מתחת לרף {threshold}.",
1405
1412
  "companionCapDetail": "בצע סבב עיבוד אוטומטי נוסף, המשך לשלב הבא תוך קבלת הפלט הנוכחי, או עצור ואפס את המשימה כדי לערוך את הקלטים ולשלוח מחדש.",
1406
- "companionStalledHeading": "{agent} עצר את לופ העיבוד מחדש לאחר {attempts} מתוך {maxAttempts} סבבים: הגרסה האחרונה חזרה ללא שינוי והדירוג נשאר מתחת לרף {threshold}.",
1413
+ "companionCapBlockedHeading": "{agent} הגיע למגבלת {attempts} ניסיונות עיבוד מחדש, וממצא חובה אחד עדיין פתוח. | {agent} הגיע למגבלת {attempts} ניסיונות עיבוד מחדש, ושני ממצאי חובה עדיין פתוחים. | {agent} הגיע למגבלת {attempts} ניסיונות עיבוד מחדש, ו-{count} ממצאי חובה עדיין פתוחים.",
1414
+ "companionCapBlockedDetail": "המשך פירושו קבלת העבודה בלי שהממצאים האלה תוקנו, ולכן מדיניות הסיכון של הריצה הזו לעולם לא תבחר בכך במקומך. בצע סבב עיבוד אוטומטי נוסף, המשך בכל זאת עם הפלט הנוכחי, או עצור ואפס את המשימה כדי לערוך את הקלטים ולשלוח מחדש.",
1415
+ "companionStalledHeading": "{agent} עצר את לופ העיבוד מחדש לאחר {attempts} מתוך {maxAttempts} סבבים: הגרסה האחרונה חזרה ללא שינוי והדירוג לא זז.",
1407
1416
  "companionStalledDetail": "הסבבים שנותרו לא נוצלו מפני שהם רק יחזרו על שיפוט שכבר ניתן. אפשר לבצע סבב נוסף בכל זאת, להמשיך לשלב הבא תוך קבלת הפלט הנוכחי, או לעצור ולאפס את המשימה כדי לערוך את הקלטים ולשלוח מחדש.",
1408
1417
  "infraAttempts": "ניסיונות תשתית",
1409
1418
  "hideInfraAttempts": "הסתר ניסיונות תשתית",
@@ -4730,7 +4739,9 @@
4730
4739
  "tracker": "מערכת מעקב",
4731
4740
  "board": "לוח",
4732
4741
  "pickBoard": "בחר לוח",
4733
- "boardPlaceholder": "מפתח פרויקט, מזהה צוות או owner/repo",
4742
+ "boardPlaceholder": "מפתח פרויקט או מזהה צוות",
4743
+ "boardFromService": "המאגר המקושר לשירות הזה",
4744
+ "boardNeedsRepo": "לשירות הזה לא מקושר מאגר, ולכן אין בו תקלות לסרוק. קשר מאגר מתוך פאנל השירות.",
4734
4745
  "boardsFailed": "לא ניתן היה לטעון את הלוחות: {reason}",
4735
4746
  "issueType": "סוג הפנייה",
4736
4747
  "issueTypeHelp": "ברירת המחדל היא bug. מתעלמים ממנו במערכות ללא סוגי פניות.",
@@ -4739,6 +4750,8 @@
4739
4750
  "labelsHelp": "מופרדות בפסיקים. כולן חייבות להופיע.",
4740
4751
  "adoptInto": "הוסף את הבאג הנבחר אל",
4741
4752
  "adoptingInto": "הבאג הנבחר יתווסף ל-{container}",
4753
+ "huntIn": "השירות לסריקה",
4754
+ "huntingIn": "נסרק המאגר המקושר ל-{container}; הבאג הנבחר יתווסף לשם.",
4742
4755
  "run": "צוד",
4743
4756
  "running": "קורא את הלוח ומעריך את מה שנמצא…",
4744
4757
  "huntFailed": "הציד נכשל",
@@ -4751,6 +4764,7 @@
4751
4764
  "noCandidates": "אין בלוח הזה באגים פתוחים ולא משויכים שתואמים.",
4752
4765
  "ratings": "השפעה {impact}/5, מורכבות {complexity}/5, רמת ודאות {confidence}",
4753
4766
  "viaModel": "הוערך על ידי {model}.",
4767
+ "scannedBoard": "נסרק {board}.",
4754
4768
  "truncated": "נסרקו רק {count} הבאגים התואמים הראשונים; בלוח הזה יש עוד.",
4755
4769
  "comments": "תגובה אחת | שתי תגובות | {count} תגובות",
4756
4770
  "confidence": {
@@ -4764,6 +4778,10 @@
4764
4778
  "failed": "לא ניתן היה להשלים את ההערכה, ולכן אלה אינם מדורגים.",
4765
4779
  "over_budget": "סביבת העבודה חרגה מתקציב ההוצאות, ולכן הבאגים האלה לא דורגו.",
4766
4780
  "empty": "אין מה להעריך."
4781
+ },
4782
+ "refusal": {
4783
+ "boardFromService": "הגששן סורק את המאגר שאליו מקושר השירות שנבחר, ולכן אין לו לוח משלו.",
4784
+ "missingBoard": "בחרו את הלוח שבו לחפש."
4767
4785
  }
4768
4786
  },
4769
4787
  "pipeline": {