@cat-factory/app 0.296.3 → 0.296.5

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.
@@ -22,7 +22,11 @@ import type {
22
22
  TaskTypeFields,
23
23
  } from '~/types/domain'
24
24
  import { DOC_KINDS, DOC_KIND_FIELDS } from '~/types/domain'
25
- import { BUG_FISHING_PHASES } from '@cat-factory/contracts'
25
+ import {
26
+ BUG_FISHING_DEFAULT_PASS_BUDGET,
27
+ BUG_FISHING_MAX_PASS_BUDGET,
28
+ BUG_FISHING_PHASES,
29
+ } from '@cat-factory/contracts'
26
30
  import { resolveComponentRegistry } from '@modular-vue/core'
27
31
  import { useReactiveSlots } from '@modular-vue/runtime'
28
32
  import type { AppSlots, ResultViewContribution } from '~/modular/slots'
@@ -138,6 +142,8 @@ const timeboxHours = ref<number | undefined>(undefined)
138
142
  // the deliberate act) plus an optional focus folded into every angle's prompt.
139
143
  const fishingPhaseIds = ref<string[]>([])
140
144
  const fishingFocus = ref('')
145
+ /** Held as a string because the input is a text field; parsed at submit, blank ⇒ the default. */
146
+ const fishingMaxPasses = ref('')
141
147
  // Spike research criteria — folded into the spike agent's prompt (see the backend `spike` kind).
142
148
  const spikeResearchQuestion = ref('')
143
149
  const spikeSuccessCriteria = ref('')
@@ -304,12 +310,37 @@ function buildCustomTypeFields(): TaskTypeFields | undefined {
304
310
  * Its own function rather than another arm of {@link buildTypeFields}, whose per-type chain is at
305
311
  * its complexity ceiling — a budget is a split trigger, not a number to raise.
306
312
  */
313
+ /**
314
+ * The typed pass budget, or undefined when the field is blank.
315
+ *
316
+ * `null` is the third answer: something was typed that is not a budget. Kept distinct from blank
317
+ * so {@link fishingMaxPassesProblem} can refuse it HERE, where the person can see which field is
318
+ * wrong, rather than letting the create call come back as a generic 422 whose only detail is a
319
+ * valibot path.
320
+ */
321
+ const fishingMaxPassesValue = computed<number | null | undefined>(() => {
322
+ const raw = fishingMaxPasses.value.trim()
323
+ if (!raw) return undefined
324
+ const parsed = Number(raw)
325
+ if (!Number.isInteger(parsed)) return null
326
+ return parsed >= 1 && parsed <= BUG_FISHING_MAX_PASS_BUDGET ? parsed : null
327
+ })
328
+
329
+ /** The message shown under the pass-budget field, or null when it is fine. */
330
+ const fishingMaxPassesProblem = computed(() =>
331
+ fishingMaxPassesValue.value === null
332
+ ? t('board.addTask.bugFishingFields.maxPasses.problem', { max: BUG_FISHING_MAX_PASS_BUDGET })
333
+ : null,
334
+ )
335
+
307
336
  function buildBugFishingFields(): TaskTypeFields | undefined {
308
337
  const f: TaskTypeFields = {}
309
338
  if (fishingPhaseIds.value.length && fishingPhaseIds.value.length < BUG_FISHING_PHASES.length) {
310
339
  f.fishingPhaseIds = [...fishingPhaseIds.value]
311
340
  }
312
341
  if (fishingFocus.value.trim()) f.fishingFocus = fishingFocus.value.trim()
342
+ const maxPasses = fishingMaxPassesValue.value
343
+ if (typeof maxPasses === 'number') f.fishingMaxPasses = maxPasses
313
344
  return Object.keys(f).length ? f : undefined
314
345
  }
315
346
 
@@ -597,6 +628,7 @@ watch(open, (isOpen) => {
597
628
  timeboxHours.value = undefined
598
629
  fishingPhaseIds.value = []
599
630
  fishingFocus.value = ''
631
+ fishingMaxPasses.value = ''
600
632
  spikeResearchQuestion.value = ''
601
633
  spikeSuccessCriteria.value = ''
602
634
  spikeOptionsToCompare.value = ''
@@ -708,6 +740,10 @@ const canAdd = computed(() => {
708
740
  return false
709
741
  // A custom type's collected form must satisfy its descriptor (the same rule the server enforces).
710
742
  if (customFieldProblems.value.length > 0) return false
743
+ // The pass budget is bounded by `taskTypeFieldsSchema`, so a value outside it is refused at
744
+ // creation whatever this form does. Refusing it here is what turns that into a message beside
745
+ // the field rather than a generic failure toast.
746
+ if (taskType.value === 'bug-fishing' && fishingMaxPassesProblem.value) return false
711
747
  return true
712
748
  })
713
749
 
@@ -1105,6 +1141,30 @@ function openReviewFrictionDialog(conflict: NonNullable<ReturnType<typeof parseC
1105
1141
  class="w-full"
1106
1142
  />
1107
1143
  </UFormField>
1144
+ <!-- An OVERRIDE, so it is hidden at the basic tier and what remains is exactly the
1145
+ shipped default it would have shown. It bites only on a codebase large enough to
1146
+ be split into territories, where the plan is territories x angles. -->
1147
+ <UFormField
1148
+ v-if="uiMode.isAdvanced"
1149
+ :label="t('board.addTask.bugFishingFields.maxPasses.label')"
1150
+ :hint="t('board.addTask.optional')"
1151
+ :description="
1152
+ t('board.addTask.bugFishingFields.maxPasses.hint', {
1153
+ count: BUG_FISHING_DEFAULT_PASS_BUDGET,
1154
+ })
1155
+ "
1156
+ :error="fishingMaxPassesProblem ?? undefined"
1157
+ >
1158
+ <UInput
1159
+ v-model="fishingMaxPasses"
1160
+ type="number"
1161
+ :min="1"
1162
+ :max="BUG_FISHING_MAX_PASS_BUDGET"
1163
+ :step="1"
1164
+ :placeholder="String(BUG_FISHING_DEFAULT_PASS_BUDGET)"
1165
+ data-testid="add-task-fishing-max-passes"
1166
+ />
1167
+ </UFormField>
1108
1168
  </div>
1109
1169
 
1110
1170
  <div v-else-if="taskType === 'spike'" class="space-y-3">
@@ -68,11 +68,44 @@ const severityRank = (s: BugFishingSeverity) => {
68
68
  }
69
69
 
70
70
  /**
71
- * Which phase's findings the reader is looking at. `null` is "everything caught so far", which
72
- * is the default because an expedition's value is the whole catch the per-phase filter exists
71
+ * A pass is an ANGLE over a TERRITORY, so a phase id alone no longer identifies one: on a
72
+ * partitioned codebase the same angle runs once per territory. The rail selects, filters and
73
+ * counts by this pair, which is also why it is derived in one place rather than spelled at each
74
+ * site.
75
+ */
76
+ function passKey(pass: { id: string; territoryId?: string | null }): string {
77
+ return `${pass.territoryId ?? ''}::${pass.id}`
78
+ }
79
+
80
+ /**
81
+ * Which pass's findings the reader is looking at. `null` is "everything caught so far", which
82
+ * is the default because an expedition's value is the whole catch — the per-pass filter exists
73
83
  * for someone working through one angle at a time, not as the primary reading.
74
84
  */
75
- const selectedPhaseId = ref<string | null>(null)
85
+ const selectedPassKey = ref<string | null>(null)
86
+
87
+ /**
88
+ * The passes grouped by the territory they fished, in plan order.
89
+ *
90
+ * One group with no territory is the pass-through: a codebase small enough to fish whole (or an
91
+ * expedition planned before territories existed) renders exactly the flat angle rail it always
92
+ * did, because its group has no header to show.
93
+ */
94
+ const territoryGroups = computed(() => {
95
+ const groups: { id: string | null; label: string | null; passes: typeof phases.value }[] = []
96
+ for (const phase of phases.value) {
97
+ const id = phase.territoryId ?? null
98
+ const last = groups[groups.length - 1]
99
+ if (last && last.id === id) last.passes.push(phase)
100
+ else groups.push({ id, label: phase.territoryLabel ?? null, passes: [phase] })
101
+ }
102
+ return groups
103
+ })
104
+
105
+ /** The expedition's plan: the budget, what it cut, and what the survey could not see. */
106
+ const plan = computed(() => state.value?.plan ?? null)
107
+ /** The cells the pass budget cut, so the window can name the ground nobody looked at. */
108
+ const unfished = computed(() => plan.value?.unfished ?? [])
76
109
 
77
110
  /** Whether findings whose decision has been made are shown. Off by default: what is left to
78
111
  * decide is the working list, and a triaged finding that stays in it reads as untriaged. */
@@ -92,10 +125,12 @@ function isOpen(f: BugFishingFinding): boolean {
92
125
  }
93
126
 
94
127
  const visibleFindings = computed<BugFishingFinding[]>(() => {
95
- const byPhase = selectedPhaseId.value
96
- ? findings.value.filter((f) => f.phaseId === selectedPhaseId.value)
128
+ const byPass = selectedPassKey.value
129
+ ? findings.value.filter(
130
+ (f) => passKey({ id: f.phaseId, territoryId: f.territoryId }) === selectedPassKey.value,
131
+ )
97
132
  : findings.value
98
- const triaged = showTriaged.value ? byPhase : byPhase.filter(isOpen)
133
+ const triaged = showTriaged.value ? byPass : byPass.filter(isOpen)
99
134
  return [...triaged].sort((a, b) => severityRank(a.severity) - severityRank(b.severity))
100
135
  })
101
136
 
@@ -106,19 +141,38 @@ const spawnedCount = computed(
106
141
  () => findings.value.filter((f) => f.spawn?.status === 'spawned').length,
107
142
  )
108
143
 
109
- /** The phase the rail has selected, when one is. */
144
+ /** The pass the rail has selected, when one is. */
110
145
  const selectedPhase = computed(() =>
111
- selectedPhaseId.value ? (phases.value.find((p) => p.id === selectedPhaseId.value) ?? null) : null,
146
+ selectedPassKey.value
147
+ ? (phases.value.find((p) => passKey(p) === selectedPassKey.value) ?? null)
148
+ : null,
112
149
  )
113
150
 
151
+ /**
152
+ * What share of its territory's manifest a settled pass reported reading, as a percentage, or
153
+ * null when there is nothing honest to show.
154
+ *
155
+ * Null covers two different absences on purpose, and neither renders as 0%. A pass that reported
156
+ * no paths said nothing about what it read, and a whole-codebase pass had no manifest to be a
157
+ * share of; showing either as "0% covered" would accuse a pass that may have read everything.
158
+ */
159
+ function coverageShare(pass: { coverage?: { filesRead: number; manifestFiles: number } | null }) {
160
+ const coverage = pass.coverage
161
+ if (!coverage || coverage.manifestFiles <= 0) return null
162
+ return Math.min(100, Math.round((coverage.filesRead / coverage.manifestFiles) * 100))
163
+ }
164
+
114
165
  /** How many angles have settled (completed or failed) — what the still-fishing banner counts. */
115
166
  const settledPhaseCount = computed(
116
167
  () => phases.value.filter((p) => p.status === 'completed' || p.status === 'failed').length,
117
168
  )
118
169
 
119
- /** How many findings each phase contributed, for the rail's per-angle count. */
120
- function phaseFindingCount(phaseId: string): number {
121
- return findings.value.filter((f) => f.phaseId === phaseId).length
170
+ /** How many findings each pass contributed, for the rail's per-pass count. */
171
+ function phaseFindingCount(pass: { id: string; territoryId?: string | null }): number {
172
+ const key = passKey(pass)
173
+ return findings.value.filter(
174
+ (f) => passKey({ id: f.phaseId, territoryId: f.territoryId }) === key,
175
+ ).length
122
176
  }
123
177
 
124
178
  /**
@@ -216,50 +270,80 @@ const PHASE_ICON: Record<string, string> = {
216
270
  type="button"
217
271
  class="mb-1 w-full rounded-md px-2 py-1.5 text-left text-[12px]"
218
272
  :class="
219
- selectedPhaseId === null
273
+ selectedPassKey === null
220
274
  ? 'bg-slate-800 text-slate-100'
221
275
  : 'text-slate-400 hover:bg-slate-800/60'
222
276
  "
223
- @click="selectedPhaseId = null"
277
+ @click="selectedPassKey = null"
224
278
  >
225
279
  {{ t('bugFishing.phases.all', { count: findings.length }) }}
226
280
  </button>
227
- <ul class="space-y-0.5">
228
- <li v-for="phase in phases" :key="phase.id">
229
- <button
230
- type="button"
231
- class="flex w-full items-start gap-2 rounded-md px-2 py-1.5 text-left"
232
- :class="
233
- selectedPhaseId === phase.id
234
- ? 'bg-slate-800 text-slate-100'
235
- : 'text-slate-400 hover:bg-slate-800/60'
236
- "
237
- :data-testid="`bug-fishing-phase-${phase.id}`"
238
- @click="selectedPhaseId = phase.id"
239
- >
240
- <UIcon
241
- :name="PHASE_ICON[phase.status] ?? 'i-lucide-circle-dashed'"
242
- class="mt-0.5 h-3.5 w-3.5 shrink-0"
243
- :class="{
244
- 'animate-spin text-sky-300': phase.status === 'fishing',
245
- 'text-emerald-400': phase.status === 'completed',
246
- 'text-amber-400': phase.status === 'failed',
247
- 'text-slate-600': phase.status === 'pending',
248
- }"
249
- />
250
- <span class="min-w-0 flex-1">
251
- <span class="block truncate text-[12px]">{{ phase.title }}</span>
252
- <span class="block text-[10px] text-slate-500">
253
- {{
254
- phase.status === 'completed' || phase.status === 'failed'
255
- ? t('bugFishing.phases.found', { count: phaseFindingCount(phase.id) })
256
- : t(`bugFishing.phases.status.${phase.status}`)
257
- }}
281
+ <!-- Grouped by TERRITORY on a partitioned codebase. A codebase small enough to fish
282
+ whole has one group with no label, which renders as the flat angle rail. -->
283
+ <div v-for="group in territoryGroups" :key="group.id ?? 'whole'" class="mb-2">
284
+ <p
285
+ v-if="group.label"
286
+ class="mb-1 mt-2 truncate px-1 text-[10px] font-semibold uppercase tracking-wide text-sky-400/80"
287
+ :title="group.label"
288
+ >
289
+ {{ group.label }}
290
+ </p>
291
+ <ul class="space-y-0.5">
292
+ <li v-for="phase in group.passes" :key="passKey(phase)">
293
+ <button
294
+ type="button"
295
+ class="flex w-full items-start gap-2 rounded-md px-2 py-1.5 text-left"
296
+ :class="
297
+ selectedPassKey === passKey(phase)
298
+ ? 'bg-slate-800 text-slate-100'
299
+ : 'text-slate-400 hover:bg-slate-800/60'
300
+ "
301
+ :data-testid="`bug-fishing-phase-${phase.id}`"
302
+ @click="selectedPassKey = passKey(phase)"
303
+ >
304
+ <UIcon
305
+ :name="PHASE_ICON[phase.status] ?? 'i-lucide-circle-dashed'"
306
+ class="mt-0.5 h-3.5 w-3.5 shrink-0"
307
+ :class="{
308
+ 'animate-spin text-sky-300': phase.status === 'fishing',
309
+ 'text-emerald-400': phase.status === 'completed',
310
+ 'text-amber-400': phase.status === 'failed',
311
+ 'text-slate-600': phase.status === 'pending',
312
+ }"
313
+ />
314
+ <span class="min-w-0 flex-1">
315
+ <span class="block truncate text-[12px]">{{ phase.title }}</span>
316
+ <span class="block text-[10px] text-slate-500">
317
+ {{
318
+ phase.status === 'completed' || phase.status === 'failed'
319
+ ? t('bugFishing.phases.found', { count: phaseFindingCount(phase) })
320
+ : t(`bugFishing.phases.status.${phase.status}`)
321
+ }}
322
+ </span>
323
+ <!-- The coverage rail. A LOW share is what tells a reader that "found nothing"
324
+ here means "did not look"; absent (a pass that reported no reads, or one
325
+ with no manifest to be a share of) shows nothing at all rather than 0%. -->
326
+ <span
327
+ v-if="coverageShare(phase) !== null"
328
+ class="mt-1 block"
329
+ :data-testid="`bug-fishing-coverage-${phase.id}`"
330
+ :title="t('bugFishing.coverage.tooltip', { percent: coverageShare(phase) })"
331
+ >
332
+ <span class="block h-0.5 w-full rounded-full bg-slate-700">
333
+ <span
334
+ class="block h-0.5 rounded-full bg-sky-500/70"
335
+ :style="{ width: `${coverageShare(phase)}%` }"
336
+ />
337
+ </span>
338
+ <span class="mt-0.5 block text-[10px] text-slate-600">
339
+ {{ t('bugFishing.coverage.share', { percent: coverageShare(phase) }) }}
340
+ </span>
341
+ </span>
258
342
  </span>
259
- </span>
260
- </button>
261
- </li>
262
- </ul>
343
+ </button>
344
+ </li>
345
+ </ul>
346
+ </div>
263
347
  </aside>
264
348
 
265
349
  <div class="min-w-0 flex-1 overflow-y-auto px-5 py-4">
@@ -303,6 +387,44 @@ const PHASE_ICON: Record<string, string> = {
303
387
  </span>
304
388
  </div>
305
389
 
390
+ <!-- What this expedition did NOT cover. Three separate facts, each with its own fix, so
391
+ none of them is folded into the others: the pass budget cut cells nobody fished, the
392
+ provider truncated the tree the territories were computed from, or the codebase could
393
+ not be surveyed at all. A cap silent about its tail teaches the reader that the tail
394
+ was clean. -->
395
+ <div
396
+ v-if="plan && (unfished.length > 0 || plan.treeTruncated || plan.surveyUnavailableReason)"
397
+ data-testid="bug-fishing-plan"
398
+ class="mb-4 rounded-lg border border-amber-500/25 bg-amber-500/5 px-3 py-2 text-[12px] text-amber-200"
399
+ >
400
+ <p v-if="plan.surveyUnavailableReason" data-testid="bug-fishing-survey-unavailable">
401
+ {{ t('bugFishing.plan.surveyUnavailable', { reason: plan.surveyUnavailableReason }) }}
402
+ </p>
403
+ <p v-if="plan.treeTruncated" data-testid="bug-fishing-tree-truncated" class="mt-1">
404
+ {{ t('bugFishing.plan.treeTruncated') }}
405
+ </p>
406
+ <template v-if="unfished.length > 0">
407
+ <p class="mt-1" data-testid="bug-fishing-unfished">
408
+ {{
409
+ t('bugFishing.plan.unfished', {
410
+ fished: phases.length,
411
+ planned: plan.plannedCells,
412
+ })
413
+ }}
414
+ </p>
415
+ <ul class="mt-1 space-y-0.5 text-[11px] text-amber-200/80">
416
+ <li v-for="cell in unfished" :key="`${cell.territoryId}::${cell.phaseId}`">
417
+ {{
418
+ t('bugFishing.plan.unfishedCell', {
419
+ territory: cell.territoryLabel,
420
+ angle: cell.phaseTitle,
421
+ })
422
+ }}
423
+ </li>
424
+ </ul>
425
+ </template>
426
+ </div>
427
+
306
428
  <!-- What the marks will run. Stated before anything is created, because the pipeline is
307
429
  the shape of the work a mark causes. -->
308
430
  <div
@@ -166,11 +166,18 @@ function selectItems(field: DescriptorField) {
166
166
  @update:model-value="(v: string) => set(field.key, v)"
167
167
  />
168
168
 
169
+ <!-- The declared bounds are INPUT HINTS only: `validateDescriptorFields` is what actually
170
+ refuses a value, here and at every other door, so a stepper that respects them is a
171
+ convenience rather than the check. `integer` steps by one; a field that admits
172
+ fractions leaves `step` unset so the browser does not round the value away. -->
169
173
  <UInput
170
174
  v-else-if="field.type === 'number'"
171
175
  :model-value="numberStr(field.key)"
172
176
  type="number"
173
177
  class="w-full font-mono"
178
+ :min="field.min"
179
+ :max="field.max"
180
+ :step="field.integer ? 1 : undefined"
174
181
  :placeholder="field.placeholder"
175
182
  @update:model-value="(v: string) => set(field.key, v === '' ? undefined : Number(v))"
176
183
  />
@@ -36,6 +36,7 @@ const back = useIntegrationBack(open)
36
36
  const RECOMMENDED_SLUGS = [
37
37
  'anthropic/claude-fable-5.1',
38
38
  'anthropic/claude-opus-5',
39
+ 'openai/gpt-6-astra',
39
40
  'openai/gpt-5.6-sol',
40
41
  'openai/gpt-5.6-terra',
41
42
  'google/gemini-3.1-pro-preview',
@@ -92,6 +92,10 @@ export type {
92
92
  BugFishingFindingKind,
93
93
  BugFishingConfidence,
94
94
  BugFishingSpawn,
95
+ BugFishingTerritory,
96
+ BugFishingPlan,
97
+ BugFishingUnfishedCell,
98
+ BugFishingCoverage,
95
99
  AgentEffortReport,
96
100
  FragmentAdherence,
97
101
  FragmentAdherenceItem,
@@ -3125,6 +3125,11 @@
3125
3125
  "focus": {
3126
3126
  "label": "Worauf konzentrieren",
3127
3127
  "placeholder": "Teilsysteme, Verzeichnisse oder die Fehlerart, die dieses Team bisher Zeit gekostet hat"
3128
+ },
3129
+ "maxPasses": {
3130
+ "label": "Maximale Durchgänge",
3131
+ "hint": "Höchstzahl der Nur-Lese-Durchgänge dieser Expedition. Leer lassen für {count}. Bei einer großen Codebasis wird jeder Blickwinkel pro Gebiet durchsucht; was das Budget streicht, wird als nicht durchsucht vermerkt.",
3132
+ "problem": "Geben Sie eine ganze Zahl zwischen 1 und {max} ein oder lassen Sie das Feld leer für den Standardwert."
3128
3133
  }
3129
3134
  }
3130
3135
  },
@@ -8438,6 +8443,19 @@
8438
8443
  "parked": "Alle Blickwinkel wurden untersucht.",
8439
8444
  "parkedWithUntriaged": "Über {count} Befund(e) wurde noch nicht entschieden.",
8440
8445
  "finish": "Sichtung abschließen"
8446
+ },
8447
+ "coverage": {
8448
+ "share": "{percent}% dieses Gebiets gelesen",
8449
+ "tooltip": "Dieser Durchgang hat angegeben, {percent}% seines Gebiets gelesen zu haben. Selbstauskunft des Agenten."
8450
+ },
8451
+ "plan": {
8452
+ "surveyUnavailable": "Die Codebasis wurde nicht kartiert, daher wurde sie als ein Ganzes durchsucht: {reason}",
8453
+ "treeTruncated": "Der Repository-Baum kam gekürzt zurück, daher decken die Gebiete unten nur den lesbaren Teil ab.",
8454
+ "unfished": "{fished} von {planned} geplanten Durchgängen ausgeführt. In diesem Lauf nicht durchsucht:",
8455
+ "unfishedCell": "{territory}, unter {angle}"
8456
+ },
8457
+ "territories": {
8458
+ "heading": "Gebiete"
8441
8459
  }
8442
8460
  }
8443
8461
  }
@@ -388,6 +388,11 @@
388
388
  "focus": {
389
389
  "label": "Where to concentrate",
390
390
  "placeholder": "Subsystems, directories, or the kind of defect that has been costing this team"
391
+ },
392
+ "maxPasses": {
393
+ "label": "Maximum passes",
394
+ "hint": "Most read-only passes this expedition may run. Leave empty for {count}. On a large codebase each angle is fished per territory, and whatever the budget cuts is recorded as unfished.",
395
+ "problem": "Enter a whole number between 1 and {max}, or leave it empty for the default."
391
396
  }
392
397
  }
393
398
  },
@@ -8736,6 +8741,19 @@
8736
8741
  "parked": "Every angle has been fished.",
8737
8742
  "parkedWithUntriaged": "{count} finding(s) still undecided.",
8738
8743
  "finish": "Finish triage"
8744
+ },
8745
+ "coverage": {
8746
+ "share": "{percent}% of this territory read",
8747
+ "tooltip": "This pass reported reading {percent}% of its territory. Self-reported by the agent."
8748
+ },
8749
+ "plan": {
8750
+ "surveyUnavailable": "The codebase was not surveyed, so this expedition fished it as one whole: {reason}",
8751
+ "treeTruncated": "The repository tree came back truncated, so the territories below cover only the part that could be read.",
8752
+ "unfished": "Fished {fished} of {planned} planned passes. Not fished this run:",
8753
+ "unfishedCell": "{territory}, under {angle}"
8754
+ },
8755
+ "territories": {
8756
+ "heading": "Territories"
8739
8757
  }
8740
8758
  }
8741
8759
  }
@@ -343,6 +343,11 @@
343
343
  "focus": {
344
344
  "label": "Dónde concentrarse",
345
345
  "placeholder": "Subsistemas, directorios o el tipo de defecto que más tiempo le está costando a este equipo"
346
+ },
347
+ "maxPasses": {
348
+ "label": "Pasadas máximas",
349
+ "hint": "Número máximo de pasadas de solo lectura de esta expedición. Déjalo vacío para {count}. En una base de código grande cada ángulo se pesca por territorio, y lo que el presupuesto recorte queda registrado como no pescado.",
350
+ "problem": "Introduce un número entero entre 1 y {max}, o déjalo vacío para el valor predeterminado."
346
351
  }
347
352
  }
348
353
  },
@@ -8438,6 +8443,19 @@
8438
8443
  "parked": "Se han explorado todos los ángulos.",
8439
8444
  "parkedWithUntriaged": "Quedan {count} hallazgo(s) sin decidir.",
8440
8445
  "finish": "Terminar el triaje"
8446
+ },
8447
+ "coverage": {
8448
+ "share": "{percent}% de este territorio leído",
8449
+ "tooltip": "Esta pasada declaró haber leído el {percent}% de su territorio. Dato declarado por el agente."
8450
+ },
8451
+ "plan": {
8452
+ "surveyUnavailable": "No se pudo inspeccionar el código, así que esta expedición lo pescó como un todo: {reason}",
8453
+ "treeTruncated": "El árbol del repositorio llegó truncado, así que los territorios de abajo cubren solo la parte que se pudo leer.",
8454
+ "unfished": "Se pescaron {fished} de {planned} pasadas previstas. Sin pescar en esta ejecución:",
8455
+ "unfishedCell": "{territory}, bajo {angle}"
8456
+ },
8457
+ "territories": {
8458
+ "heading": "Territorios"
8441
8459
  }
8442
8460
  }
8443
8461
  }
@@ -343,6 +343,11 @@
343
343
  "focus": {
344
344
  "label": "Où se concentrer",
345
345
  "placeholder": "Sous-systèmes, répertoires, ou le type de défaut qui coûte le plus de temps à cette équipe"
346
+ },
347
+ "maxPasses": {
348
+ "label": "Passes maximum",
349
+ "hint": "Nombre maximal de passes en lecture seule de cette expédition. Laisser vide pour {count}. Sur un gros code, chaque angle est pêché par territoire, et ce que le budget coupe est consigné comme non pêché.",
350
+ "problem": "Saisissez un nombre entier compris entre 1 et {max}, ou laissez le champ vide pour la valeur par défaut."
346
351
  }
347
352
  }
348
353
  },
@@ -8438,6 +8443,19 @@
8438
8443
  "parked": "Tous les angles ont été explorés.",
8439
8444
  "parkedWithUntriaged": "{count} constatation(s) encore non tranchée(s).",
8440
8445
  "finish": "Terminer le tri"
8446
+ },
8447
+ "coverage": {
8448
+ "share": "{percent}% de ce territoire lu",
8449
+ "tooltip": "Cette passe déclare avoir lu {percent}% de son territoire. Donnée déclarée par l'agent."
8450
+ },
8451
+ "plan": {
8452
+ "surveyUnavailable": "Le code n'a pas été cartographié, cette expédition l'a donc pêché d'un seul tenant : {reason}",
8453
+ "treeTruncated": "L'arbre du dépôt est revenu tronqué : les territoires ci-dessous ne couvrent que la partie lisible.",
8454
+ "unfished": "{fished} passes pêchées sur {planned} prévues. Non pêché lors de cette exécution :",
8455
+ "unfishedCell": "{territory}, sous {angle}"
8456
+ },
8457
+ "territories": {
8458
+ "heading": "Territoires"
8441
8459
  }
8442
8460
  }
8443
8461
  }
@@ -343,6 +343,11 @@
343
343
  "focus": {
344
344
  "label": "במה להתמקד",
345
345
  "placeholder": "תת-מערכות, תיקיות או סוג התקלות שעולה לצוות הזה הכי הרבה זמן"
346
+ },
347
+ "maxPasses": {
348
+ "label": "מספר מעברים מרבי",
349
+ "hint": "מספר המעברים לקריאה בלבד המרבי של המשלחת הזו. השאירו ריק עבור {count}. בבסיס קוד גדול כל זווית נסרקת לכל טריטוריה בנפרד, ומה שהתקציב חותך נרשם כלא נסרק.",
350
+ "problem": "הזינו מספר שלם בין 1 ל-{max}, או השאירו ריק לערך ברירת המחדל."
346
351
  }
347
352
  }
348
353
  },
@@ -8438,6 +8443,19 @@
8438
8443
  "parked": "כל הזוויות נסרקו.",
8439
8444
  "parkedWithUntriaged": "נותרו {count} ממצאים ללא הכרעה.",
8440
8445
  "finish": "סיום המיון"
8446
+ },
8447
+ "coverage": {
8448
+ "share": "{percent}% מהטריטוריה הזו נקראו",
8449
+ "tooltip": "המעבר הזה דיווח שקרא {percent}% מהטריטוריה שלו. דיווח עצמי של הסוכן."
8450
+ },
8451
+ "plan": {
8452
+ "surveyUnavailable": "בסיס הקוד לא נסקר, ולכן המשלחת סרקה אותו כמקשה אחת: {reason}",
8453
+ "treeTruncated": "עץ המאגר חזר קטוע, ולכן הטריטוריות שלהלן מכסות רק את החלק שניתן היה לקרוא.",
8454
+ "unfished": "בוצעו {fished} מתוך {planned} מעברים מתוכננים. לא נסרק בהרצה הזו:",
8455
+ "unfishedCell": "{territory}, בזווית {angle}"
8456
+ },
8457
+ "territories": {
8458
+ "heading": "טריטוריות"
8441
8459
  }
8442
8460
  }
8443
8461
  }
@@ -3125,6 +3125,11 @@
3125
3125
  "focus": {
3126
3126
  "label": "Dove concentrarsi",
3127
3127
  "placeholder": "Sottosistemi, cartelle o il tipo di difetto che sta costando più tempo a questo team"
3128
+ },
3129
+ "maxPasses": {
3130
+ "label": "Passaggi massimi",
3131
+ "hint": "Numero massimo di passaggi in sola lettura di questa spedizione. Lascia vuoto per {count}. Su una codebase grande ogni angolazione viene pescata per territorio, e ciò che il budget taglia resta registrato come non pescato.",
3132
+ "problem": "Inserisci un numero intero compreso tra 1 e {max}, oppure lascia vuoto per il valore predefinito."
3128
3133
  }
3129
3134
  }
3130
3135
  },
@@ -8438,6 +8443,19 @@
8438
8443
  "parked": "Tutte le angolazioni sono state esplorate.",
8439
8444
  "parkedWithUntriaged": "Restano {count} rilievi senza decisione.",
8440
8445
  "finish": "Concludi il triage"
8446
+ },
8447
+ "coverage": {
8448
+ "share": "{percent}% di questo territorio letto",
8449
+ "tooltip": "Questo passaggio ha dichiarato di aver letto il {percent}% del suo territorio. Dato dichiarato dall'agente."
8450
+ },
8451
+ "plan": {
8452
+ "surveyUnavailable": "La codebase non è stata mappata, quindi questa spedizione l'ha pescata per intero: {reason}",
8453
+ "treeTruncated": "L'albero del repository è arrivato troncato, quindi i territori qui sotto coprono solo la parte leggibile.",
8454
+ "unfished": "Pescati {fished} passaggi su {planned} previsti. Non pescati in questa esecuzione:",
8455
+ "unfishedCell": "{territory}, sotto {angle}"
8456
+ },
8457
+ "territories": {
8458
+ "heading": "Territori"
8441
8459
  }
8442
8460
  }
8443
8461
  }
@@ -343,6 +343,11 @@
343
343
  "focus": {
344
344
  "label": "重点を置く箇所",
345
345
  "placeholder": "サブシステム、ディレクトリ、またはこのチームで手間になっている不具合の種類"
346
+ },
347
+ "maxPasses": {
348
+ "label": "最大パス数",
349
+ "hint": "この遠征が実行できる読み取り専用パスの上限です。空欄なら {count} 回。大規模なコードベースでは各観点を領域ごとに探索し、予算で削られた分は未探索として記録されます。",
350
+ "problem": "1 から {max} までの整数を入力してください。空欄のままにすると既定値が使われます。"
346
351
  }
347
352
  }
348
353
  },
@@ -8438,6 +8443,19 @@
8438
8443
  "parked": "すべての観点の探索が終わりました。",
8439
8444
  "parkedWithUntriaged": "未判断の指摘が {count} 件あります。",
8440
8445
  "finish": "トリアージを終了"
8446
+ },
8447
+ "coverage": {
8448
+ "share": "この領域の {percent}% を読了",
8449
+ "tooltip": "このパスは自身の領域の {percent}% を読んだと報告しました。エージェントの自己申告です。"
8450
+ },
8451
+ "plan": {
8452
+ "surveyUnavailable": "コードベースを調査できなかったため、この遠征は全体を一括で探索しました: {reason}",
8453
+ "treeTruncated": "リポジトリのツリーが切り詰められて返されたため、以下の領域は読み取れた範囲のみを対象としています。",
8454
+ "unfished": "予定 {planned} パス中 {fished} パスを実施しました。今回未探索:",
8455
+ "unfishedCell": "{territory} の {angle}"
8456
+ },
8457
+ "territories": {
8458
+ "heading": "領域"
8441
8459
  }
8442
8460
  }
8443
8461
  }
@@ -343,6 +343,11 @@
343
343
  "focus": {
344
344
  "label": "Na czym się skupić",
345
345
  "placeholder": "Podsystemy, katalogi albo rodzaj usterki, który najbardziej kosztuje ten zespół"
346
+ },
347
+ "maxPasses": {
348
+ "label": "Maksymalna liczba przejść",
349
+ "hint": "Najwyższa liczba przejść tylko do odczytu w tej wyprawie. Zostaw puste, aby użyć {count}. W dużej bazie kodu każdy kąt przeszukiwany jest osobno dla każdego terytorium, a to, co utnie budżet, zapisujemy jako nieprzeszukane.",
350
+ "problem": "Podaj liczbę całkowitą od 1 do {max} albo pozostaw pole puste, aby użyć wartości domyślnej."
346
351
  }
347
352
  }
348
353
  },
@@ -8438,6 +8443,19 @@
8438
8443
  "parked": "Przeszukano wszystkie perspektywy.",
8439
8444
  "parkedWithUntriaged": "Pozostało {count} nierozstrzygniętych znalezisk.",
8440
8445
  "finish": "Zakończ segregację"
8446
+ },
8447
+ "coverage": {
8448
+ "share": "Przeczytano {percent}% tego terytorium",
8449
+ "tooltip": "To przejście zgłosiło przeczytanie {percent}% swojego terytorium. Dane podane przez agenta."
8450
+ },
8451
+ "plan": {
8452
+ "surveyUnavailable": "Baza kodu nie została zbadana, więc ta wyprawa przeszukała ją w całości: {reason}",
8453
+ "treeTruncated": "Drzewo repozytorium wróciło obcięte, więc terytoria poniżej obejmują tylko tę część, którą udało się odczytać.",
8454
+ "unfished": "Wykonano {fished} z {planned} zaplanowanych przejść. Nieprzeszukane w tym uruchomieniu:",
8455
+ "unfishedCell": "{territory}, pod kątem {angle}"
8456
+ },
8457
+ "territories": {
8458
+ "heading": "Terytoria"
8441
8459
  }
8442
8460
  }
8443
8461
  }
@@ -343,6 +343,11 @@
343
343
  "focus": {
344
344
  "label": "Nereye yoğunlaşılsın",
345
345
  "placeholder": "Alt sistemler, dizinler ya da bu ekibe en çok zaman kaybettiren kusur türü"
346
+ },
347
+ "maxPasses": {
348
+ "label": "En fazla geçiş",
349
+ "hint": "Bu seferin yapabileceği en fazla salt okunur geçiş sayısı. {count} için boş bırakın. Büyük bir kod tabanında her açı her bölge için ayrı taranır ve bütçenin kestikleri taranmamış olarak kaydedilir.",
350
+ "problem": "1 ile {max} arasında bir tam sayı girin veya varsayılan değer için boş bırakın."
346
351
  }
347
352
  }
348
353
  },
@@ -8438,6 +8443,19 @@
8438
8443
  "parked": "Tüm açılar tarandı.",
8439
8444
  "parkedWithUntriaged": "{count} bulgu hâlâ karara bağlanmadı.",
8440
8445
  "finish": "Triyajı bitir"
8446
+ },
8447
+ "coverage": {
8448
+ "share": "Bu bölgenin %{percent} kadarı okundu",
8449
+ "tooltip": "Bu geçiş, bölgesinin %{percent} kadarını okuduğunu bildirdi. Ajanın kendi beyanı."
8450
+ },
8451
+ "plan": {
8452
+ "surveyUnavailable": "Kod tabanı taranamadı, bu nedenle bu sefer tek parça olarak tarandı: {reason}",
8453
+ "treeTruncated": "Depo ağacı kırpılmış olarak döndü, bu nedenle aşağıdaki bölgeler yalnızca okunabilen kısmı kapsıyor.",
8454
+ "unfished": "Planlanan {planned} geçişten {fished} tanesi yapıldı. Bu çalışmada taranmayanlar:",
8455
+ "unfishedCell": "{angle} açısından {territory}"
8456
+ },
8457
+ "territories": {
8458
+ "heading": "Bölgeler"
8441
8459
  }
8442
8460
  }
8443
8461
  }
@@ -343,6 +343,11 @@
343
343
  "focus": {
344
344
  "label": "На чому зосередитися",
345
345
  "placeholder": "Підсистеми, каталоги або тип дефектів, що найбільше коштує цій команді"
346
+ },
347
+ "maxPasses": {
348
+ "label": "Максимум проходів",
349
+ "hint": "Найбільша кількість проходів лише для читання в цій експедиції. Залиште порожнім, щоб узяти {count}. У великій кодовій базі кожен кут перевіряється для кожної території, а зрізане бюджетом записується як неперевірене.",
350
+ "problem": "Введіть ціле число від 1 до {max} або залиште поле порожнім, щоб використати типове значення."
346
351
  }
347
352
  }
348
353
  },
@@ -8438,6 +8443,19 @@
8438
8443
  "parked": "Пройдено всі кути.",
8439
8444
  "parkedWithUntriaged": "Залишилося {count} нерозглянутих знахідок.",
8440
8445
  "finish": "Завершити сортування"
8446
+ },
8447
+ "coverage": {
8448
+ "share": "Прочитано {percent}% цієї території",
8449
+ "tooltip": "Цей прохід повідомив, що прочитав {percent}% своєї території. Дані з власних слів агента."
8450
+ },
8451
+ "plan": {
8452
+ "surveyUnavailable": "Кодову базу не обстежено, тож ця експедиція шукала в ній як у єдиному цілому: {reason}",
8453
+ "treeTruncated": "Дерево репозиторію повернулося обрізаним, тож території нижче охоплюють лише прочитану частину.",
8454
+ "unfished": "Виконано {fished} із {planned} запланованих проходів. Не перевірено цього разу:",
8455
+ "unfishedCell": "{territory}, під кутом {angle}"
8456
+ },
8457
+ "territories": {
8458
+ "heading": "Території"
8441
8459
  }
8442
8460
  }
8443
8461
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cat-factory/app",
3
- "version": "0.296.3",
3
+ "version": "0.296.5",
4
4
  "description": "Reusable Nuxt layer for the Agent Architecture Board SPA (components, stores, composables, pages). Consume it from a thin deployment app via `extends: ['@cat-factory/app']` and point it at your backend with NUXT_PUBLIC_API_BASE. See deploy/frontend for an example.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -18,7 +18,7 @@
18
18
  "access": "public"
19
19
  },
20
20
  "dependencies": {
21
- "@cat-factory/contracts": "0.346.2",
21
+ "@cat-factory/contracts": "0.347.0",
22
22
  "@modular-frontend/core": "0.6.0",
23
23
  "@modular-vue/core": "^1.5.0",
24
24
  "@modular-vue/journeys": "^1.4.0",
@@ -44,7 +44,7 @@
44
44
  },
45
45
  "devDependencies": {
46
46
  "@toad-contracts/testing": "0.3.2",
47
- "happy-dom": "^20.13.2",
47
+ "happy-dom": "^20.14.0",
48
48
  "msw": "^2.15.0",
49
49
  "nuxt": "^4.5.2",
50
50
  "typescript": "^6.0.3",