@cat-factory/app 0.270.2 → 0.271.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.
package/README.md CHANGED
@@ -348,8 +348,12 @@ picking what each of them runs on are halves of the same job.
348
348
  The builder's palette narrows on two axes, and both controls sit on one row above the catalog
349
349
  (`PipelinePurposeSelect` above `AgentTierSelect`), each with its own "n hidden" hint so neither
350
350
  narrowing reads as an empty catalog. The tier says how deep to look; the **purpose** says what
351
- the pipeline is for (`build` / `document` / `review` / `research` / `planning`), and the palette
352
- drops the categories that purpose has no use for. It reaches past the palette: the saved-pipeline
351
+ the pipeline is for (`build` / `bugfix` / `document` / `review` / `research` / `planning`), and the
352
+ palette drops the categories that purpose has no use for. `bugfix` is the one pair that shares a
353
+ row with another member: it ships code exactly as `build` does, and differs only in being offered
354
+ to a `bug` task and withheld from a `feature` one, because a preset that investigates a defect
355
+ report and writes a failing reproduction test has neither input on a feature. It reaches past the
356
+ palette: the saved-pipeline
353
357
  library in the builder's third column lists the pipelines built for the purpose being edited, so
354
358
  one dial narrows both ends of the slideover. The purpose is not a view preference either: it is
355
359
  saved on the pipeline and also decides which task pickers offer it, which is why the control
@@ -380,13 +380,14 @@ const selectedModelPresetLabel = computed(() => {
380
380
  // still browsable.
381
381
  const fragmentPool = computed(() => fragments.forBlockType(frame.value?.type ?? 'service'))
382
382
 
383
- // Hide UI-testing pipelines (`tester-ui` / `visual-confirmation`) when the target frame has no
384
- // UI to exercise they'd be refused server-side (see utils/pipeline + the backend gate). Also
385
- // hide `'recurring'`-only pipelines (a one-off task start of one is refused at run start) and every
386
- // pipeline whose purpose doesn't match the chosen task type (a doc task authors a doc, a review task
387
- // reviews a PR, and a `feature`/`bug` task ships code so it is offered build + research only, not
388
- // the doc/review/planning presets). `blockLevel: 'task'` is passed literally because this modal only
389
- // ever creates a task leaf, which also drops the three planning presets the backend would refuse.
383
+ // Hide UI-testing pipelines when the target frame has no UI for them to reach — a step scoped to
384
+ // a frontend service excuses itself, so this only drops an UNCONDITIONAL one (see utils/pipeline
385
+ // + the backend gate). Also hide `'recurring'`-only pipelines (a one-off task start of one is
386
+ // refused at run start) and every pipeline whose purpose doesn't match the chosen task type (a doc
387
+ // task authors a doc, a review task reviews a PR, a `bug` task ships code and may reach for a
388
+ // bugfix preset, and a `feature` gets that set minus the bugfix ones, which have no defect report
389
+ // to investigate). `blockLevel: 'task'` is passed literally because this modal only ever creates a
390
+ // task leaf, which also drops the three planning presets the backend would refuse.
390
391
  // Re-filters as the chosen task type changes.
391
392
  const selectablePipelines = computed(() =>
392
393
  pipelines.pipelines.filter((p) =>
@@ -416,7 +417,15 @@ const DEFAULT_PIPELINE_FOR_TYPE: Partial<Record<TaskTypeChoice, string>> = {
416
417
  * deliberately does not name), the build rung this interface mode defaults to. Basic mode gets the
417
418
  * fixed Standard build, advanced the Adaptive one; `defaultBuildPipelineId` owns that rule so the
418
419
  * create form and the task card's plain "Start" cannot disagree about it. Empty when the resolved
419
- * preset is not in this workspace's library (an older seed, or a retired rung).
420
+ * preset is not in this workspace's library (an older seed, or a retired rung) or when the picker
421
+ * would not OFFER it for `type`.
422
+ *
423
+ * That second check is why it re-runs the picker's own predicate rather than scanning the whole
424
+ * library: a workspace whose declared interactive default is a bugfix preset would otherwise open a
425
+ * `feature` form on a selection with no matching row, and create the task pinned to a pipeline the
426
+ * same screen says is not allowed for its type. Parameterised by `type` rather than reading
427
+ * `selectablePipelines`, so the answer cannot depend on whether the watcher fires before or after
428
+ * the ref it filters on has settled.
420
429
  *
421
430
  * ONE definition, read by both the type watcher and the open-reset. They used to compute it
422
431
  * separately, the reset consulting `DEFAULT_PIPELINE_FOR_TYPE` alone and falling to `''` for every
@@ -433,7 +442,10 @@ function defaultPipelineIdFor(type: TaskTypeChoice): string {
433
442
  // and the task card's plain Start still cannot disagree (see `declaredDefaultId`).
434
443
  pipelines.declaredDefaultId('interactive') ??
435
444
  defaultBuildPipelineId(uiMode.isAdvanced)
436
- return pipelines.pipelines.some((p) => p.id === preset) ? preset : ''
445
+ const resolved = pipelines.pipelines.find((p) => p.id === preset)
446
+ const offered =
447
+ !!resolved && pipelineAllowedForManualStart(resolved, frame.value, board.blocks, type, 'task')
448
+ return offered ? preset : ''
437
449
  }
438
450
 
439
451
  watch(taskType, (next) => {
@@ -131,7 +131,8 @@ function defaultRecurrence(): Recurrence {
131
131
  }
132
132
  }
133
133
 
134
- // Hide UI-testing pipelines when the frame has no UI to exercise they'd be refused at run start.
134
+ // Hide a pipeline whose UI-testing step would REACH a frame with no UI (one scoped to a frontend
135
+ // service excuses itself) — it would be refused at run start.
135
136
  // Also hide `'one-off'`-only pipelines: attaching one to a schedule is refused server-side.
136
137
  const selectablePipelines = computed(() =>
137
138
  pipelines.pipelines.filter((p) => pipelineAllowedForSchedule(p, frame.value, board.blocks)),
@@ -1,6 +1,6 @@
1
1
  <script setup lang="ts">
2
- // The pipeline-PURPOSE control: what the pipeline being built exists to do (build, document,
3
- // review, research, plan). It sits beside `AgentTierSelect` at the top of the agent palette
2
+ // The pipeline-PURPOSE control: what the pipeline being built exists to do (build, fix a bug,
3
+ // document, review, research, plan). It sits beside `AgentTierSelect` at the top of the agent palette
4
4
  // because the two are the same kind of dial (each narrows the catalog below to what is worth
5
5
  // offering), and a filter the user cannot see is a catalog that looks incomplete for no reason.
6
6
  //
@@ -25,6 +25,7 @@ const emit = defineEmits<{ (e: 'update:purpose', purpose: PipelinePurpose): void
25
25
  // typed-message-keys check covers them, the same shape `AgentTierSelect` uses for its tiers.
26
26
  const PURPOSE_LABELS = computed<Record<PipelinePurpose, string>>(() => ({
27
27
  build: t('pipeline.builder.purposeOption.build'),
28
+ bugfix: t('pipeline.builder.purposeOption.bugfix'),
28
29
  document: t('pipeline.builder.purposeOption.document'),
29
30
  review: t('pipeline.builder.purposeOption.review'),
30
31
  research: t('pipeline.builder.purposeOption.research'),
@@ -13,7 +13,7 @@ import StepFragmentAdherence from '~/components/panels/StepFragmentAdherence.vue
13
13
  import BinaryOutputReport from '~/components/binaryOutput/BinaryOutputReport.vue'
14
14
  import EnvironmentStatusPanel from '~/components/environments/EnvironmentStatusPanel.vue'
15
15
  import FrontendBindingsResolved from '~/components/panels/inspector/FrontendBindingsResolved.vue'
16
- import { UI_TESTER_AGENT_KIND } from '@cat-factory/contracts'
16
+ import { UI_TESTER_AGENT_KIND, blockingReviewComments } from '@cat-factory/contracts'
17
17
  import type { GateApprovalRefusal } from '@cat-factory/contracts'
18
18
  import ProvisioningLogsDrawer from '~/components/provisioning/ProvisioningLogsDrawer.vue'
19
19
  import IterationCapPrompt from '~/components/pipeline/IterationCapPrompt.vue'
@@ -171,10 +171,31 @@ const proposalEditable = computed(() => step.value?.outputIsRendered !== true)
171
171
  // approve/request-changes/reject rail, it shows the shared iteration-cap prompt
172
172
  // (one more round / proceed / stop & reset), resolved through its own endpoint.
173
173
  const companionExceeded = computed(() => approvalPending.value && !!step.value?.companion?.exceeded)
174
+ /**
175
+ * The must-fix findings the reviewer left open on its last round.
176
+ *
177
+ * They are why the cap prompts read differently, and the difference is not cosmetic: a cap
178
+ * reached on the rating alone is the loop reporting that this is as good as it got, while an open
179
+ * blocker is the reviewer saying the work must not go on as it stands. That second one is also the
180
+ * park no risk policy will answer, so the person reading it is the only route past it and should
181
+ * be told what they are being asked to overrule.
182
+ */
183
+ const blockingFindings = computed(() => blockingReviewComments(latestVerdict.value?.comments))
174
184
  // The SAME park, reached for the opposite reason: the loop was abandoned with rounds still on the
175
185
  // budget because the producer handed back the work it was asked to change and the rating did not
176
186
  // move. The three choices are identical, so this only picks the wording — the cap copy states a
177
187
  // spent limit, which is a false claim about this park (`companion.stalled`).
188
+ //
189
+ // It can hold TOGETHER with `blockingFindings`, and that pair is what splits the wording across
190
+ // the two slots rather than ranking them: the HEADING says how the loop ended (a stalled one did
191
+ // not reach its limit, so only it may say so) and the DETAIL says what this person is being asked
192
+ // to decide (an open blocker outranks a bar that went unmet, and its copy claims nothing about
193
+ // rounds). Neither slot can then state something untrue of the park it is describing.
194
+ //
195
+ // Which is why the stalled heading claims nothing about the RATING either. Standing still is
196
+ // unchanged output at an unmoved rating (`companionLoopStalled`), never a rating under the bar: a
197
+ // round held by an open blocker fails at a rating that cleared it, and the copy said "the rating
198
+ // held below the 80% bar" over a 95% one. What the number was is on the verdict card above.
178
199
  const companionStalled = computed(() => companionExceeded.value && !!step.value?.companion?.stalled)
179
200
  // A park a DEDICATED window owns (fork choice / follow-up triage): the generic approve
180
201
  // resolver refuses these server-side, so the rail is replaced by a redirect to that window.
@@ -492,10 +513,12 @@ async function copyOutput() {
492
513
  :step-index="ctx?.stepIndex ?? null"
493
514
  />
494
515
 
495
- <!-- companion rework budget spent, OR the loop abandoned early as unproductive:
496
- the shared iteration-cap decision (one more round / proceed with the current
497
- output / stop & reset). One prompt, two headings — the choices are the same but
498
- the reason is not, and the spent-limit wording is untrue of a stalled loop. -->
516
+ <!-- companion rework budget spent, OR the loop abandoned early as unproductive,
517
+ with or without must-fix findings still open: the shared iteration-cap decision
518
+ (one more round / proceed with the current output / stop & reset). One prompt,
519
+ and the two slots are picked on different facts the choices are the same but
520
+ the reason is not, the spent-limit wording is untrue of a stalled loop, and an
521
+ open blocker is what the person is actually being asked to overrule. -->
499
522
  <IterationCapPrompt
500
523
  v-if="companionExceeded"
501
524
  :heading="
@@ -504,18 +527,29 @@ async function copyOutput() {
504
527
  agent: agent.label,
505
528
  attempts: step.companion?.attempts,
506
529
  maxAttempts: step.companion?.maxAttempts,
507
- threshold: pctOf(latestVerdict?.threshold ?? 0),
508
- })
509
- : t('panels.stepDetail.companionCapHeading', {
510
- agent: agent.label,
511
- attempts: step.companion?.maxAttempts,
512
- threshold: pctOf(latestVerdict?.threshold ?? 0),
513
530
  })
531
+ : blockingFindings.length
532
+ ? t(
533
+ 'panels.stepDetail.companionCapBlockedHeading',
534
+ {
535
+ agent: agent.label,
536
+ attempts: step.companion?.maxAttempts,
537
+ count: blockingFindings.length,
538
+ },
539
+ blockingFindings.length,
540
+ )
541
+ : t('panels.stepDetail.companionCapHeading', {
542
+ agent: agent.label,
543
+ attempts: step.companion?.maxAttempts,
544
+ threshold: pctOf(latestVerdict?.threshold ?? 0),
545
+ })
514
546
  "
515
547
  :detail="
516
- companionStalled
517
- ? t('panels.stepDetail.companionStalledDetail')
518
- : t('panels.stepDetail.companionCapDetail')
548
+ blockingFindings.length
549
+ ? t('panels.stepDetail.companionCapBlockedDetail')
550
+ : companionStalled
551
+ ? t('panels.stepDetail.companionStalledDetail')
552
+ : t('panels.stepDetail.companionCapDetail')
519
553
  "
520
554
  :loading="resolvingCap"
521
555
  @resolve="resolveCompanionCap"
@@ -1,6 +1,12 @@
1
1
  <script setup lang="ts">
2
2
  import { computed } from 'vue'
3
+ import {
4
+ bySeverityWorstFirst,
5
+ isReviewCommentSeverity,
6
+ type ReviewCommentSeverity,
7
+ } from '@cat-factory/contracts'
3
8
  import type { AgentState, PipelineStep, CompanionVerdict, StepApproval } from '~/types/execution'
9
+ import type { BadgeColor } from '~/utils/badge'
4
10
  import { subtaskIconClass } from '~/utils/pipelineRender'
5
11
  import StepModelActivity from '~/components/observability/StepModelActivity.vue'
6
12
  import StepContainerStatus from '~/components/panels/StepContainerStatus.vue'
@@ -65,6 +71,71 @@ const ITEM_ICON: Record<string, string> = {
65
71
 
66
72
  const pctOf = (n: number) => `${Math.round(n * 100)}%`
67
73
 
74
+ /**
75
+ * The colour each finding grade renders at. `ungraded` is its own member rather than a fallback
76
+ * arm: a person's comment carries no severity and neither does a verdict recorded before reviewers
77
+ * graded anything, and painting either of those `major` would put a level on the screen that
78
+ * nobody chose. `unrecognized` is the same argument for the other direction (see
79
+ * {@link findingGrade}). An exhaustive `Record` so a severity added to the contract fails to
80
+ * compile here, typed against the shared `BadgeColor` rather than a hand-picked subset of it.
81
+ */
82
+ const SEVERITY_COLOR: Record<ReviewCommentSeverity | 'ungraded' | 'unrecognized', BadgeColor> = {
83
+ blocker: 'error',
84
+ major: 'warning',
85
+ minor: 'neutral',
86
+ ungraded: 'neutral',
87
+ unrecognized: 'neutral',
88
+ }
89
+
90
+ /**
91
+ * How one finding's grade renders: the level itself, or which of the two NON-levels it is.
92
+ *
93
+ * `unrecognized` is what a level this build has retired reads as. The severity vocabulary is closed
94
+ * but persisted, and a stored verdict is mapped onto the type rather than re-parsed, so the schema's
95
+ * `major` fallback never runs on this path (contracts' `isReviewCommentSeverity` states the rule).
96
+ * Left unnarrowed the value indexes both maps and comes back `undefined`, which renders an unstyled
97
+ * badge over a raw i18n key; guessed onto a current level it would show an urgency nobody graded, on
98
+ * the panel asking a person to act on it. So it is NAMED, and the copy carries the stored value.
99
+ */
100
+ function findingGrade(severity: string | undefined): {
101
+ key: ReviewCommentSeverity | 'ungraded' | 'unrecognized'
102
+ level: string
103
+ } {
104
+ if (severity === undefined) return { key: 'ungraded', level: '' }
105
+ if (isReviewCommentSeverity(severity)) return { key: severity, level: severity }
106
+ return { key: 'unrecognized', level: severity }
107
+ }
108
+
109
+ /**
110
+ * Each round paired with its findings, worst first (the order the reviewer's asks should be worked
111
+ * in) and each finding with its resolved grade.
112
+ *
113
+ * A `computed` rather than methods the template calls, because a run panel re-renders on every
114
+ * pushed instance update while the template needs the list twice per round (the `v-if` and the
115
+ * `v-for`) and the grade three times per finding: as methods that is a copy, a sort and a narrowing
116
+ * per reader per push, all off state that only changes when a verdict lands.
117
+ */
118
+ const verdictRounds = computed(() =>
119
+ props.companionVerdicts.map((verdict) => ({
120
+ verdict,
121
+ findings: bySeverityWorstFirst(verdict.comments ?? []).map((finding) => ({
122
+ body: finding.body,
123
+ grade: findingGrade(finding.severity),
124
+ })),
125
+ })),
126
+ )
127
+
128
+ /**
129
+ * Whether this round's rating actually reached its bar.
130
+ *
131
+ * Distinct from the verdict's own `passed`, which is what the ENGINE decided and can be `false` at a
132
+ * rating well above the threshold: an open `blocker` holds the step whatever the number says. The
133
+ * two were one expression, so the panel printed a false inequality over the findings that explained
134
+ * it. `>=` matches kernel's `disposeCompanionVerdict`, where a threshold typed by an operator must be
135
+ * met exactly by a rating equal to it.
136
+ */
137
+ const ratingMeetsBar = (verdict: CompanionVerdict) => verdict.rating >= verdict.threshold
138
+
68
139
  const APPROVAL_STATUS_KEYS: Record<StepApproval['status'], string> = {
69
140
  pending: 'panels.stepMeta.approvalStatus.pending',
70
141
  approved: 'panels.stepMeta.approvalStatus.approved',
@@ -278,17 +349,23 @@ async function copyRunId() {
278
349
  <span class="text-[11px] uppercase tracking-wide text-slate-500">
279
350
  {{ t('panels.stepMeta.companionReview') }}
280
351
  </span>
352
+ <!-- The COLOUR is the verdict (`passed`) and the GLYPH is the arithmetic, which are no
353
+ longer the same fact: a round holding an open `blocker` fails at a rating that cleared
354
+ its bar, and reading the inequality off `passed` printed "95% < 80%" over the findings
355
+ explaining why. -->
281
356
  <UBadge :color="latestVerdict?.passed ? 'success' : 'warning'" variant="subtle" size="sm">
282
357
  {{ pctOf(latestVerdict!.rating) }}
283
- {{ latestVerdict?.passed ? '≥' : '<' }} {{ pctOf(latestVerdict!.threshold) }}
358
+ {{ ratingMeetsBar(latestVerdict!) ? '≥' : '<' }} {{ pctOf(latestVerdict!.threshold) }}
284
359
  </UBadge>
285
360
  </div>
286
- <!-- One card per correction round: the score on its own line, then the reviewer's
287
- challenge as rendered markdown. The feedback used to trail the score inside the same
288
- line, which turned a multi-point review into one unreadable run of text. -->
361
+ <!-- One card per correction round: the score on its own line, then the reviewer's verdict
362
+ as rendered markdown, then its graded findings worst first. The feedback used to trail
363
+ the score inside the same line, which turned a multi-point review into one unreadable
364
+ run of text; the findings used not to be rendered at all, so a "must fix" holding the
365
+ run was invisible to the person being asked to resolve it. -->
289
366
  <ol class="mt-2 space-y-2">
290
367
  <li
291
- v-for="(v, i) in companionVerdicts"
368
+ v-for="({ verdict: v, findings }, i) in verdictRounds"
292
369
  :key="i"
293
370
  data-testid="companion-verdict"
294
371
  class="relative rounded-lg border border-slate-800 bg-slate-900/60 px-3 py-2"
@@ -304,14 +381,36 @@ async function copyRunId() {
304
381
  {{ i + 1 }}
305
382
  </span>
306
383
  <span :class="v.passed ? 'text-emerald-300' : 'text-amber-300'">
307
- {{ pctOf(v.rating) }} {{ v.passed ? '≥' : '<' }} {{ pctOf(v.threshold) }}
384
+ {{ pctOf(v.rating) }} {{ ratingMeetsBar(v) ? '≥' : '<' }} {{ pctOf(v.threshold) }}
308
385
  </span>
309
386
  </div>
310
387
  <MarkdownProse
311
388
  v-if="v.feedback"
312
389
  :text="v.feedback"
390
+ data-testid="companion-verdict-summary"
313
391
  class="mt-1.5 pe-6 text-[12px] leading-relaxed text-slate-300"
314
392
  />
393
+ <ul v-if="findings.length" class="mt-2 space-y-1.5">
394
+ <li
395
+ v-for="(finding, fi) in findings"
396
+ :key="fi"
397
+ data-testid="companion-finding"
398
+ class="flex gap-2"
399
+ >
400
+ <UBadge
401
+ :color="SEVERITY_COLOR[finding.grade.key]"
402
+ variant="subtle"
403
+ size="sm"
404
+ class="mt-px h-4 shrink-0"
405
+ >
406
+ {{ t(`panels.stepMeta.findingSeverity.${finding.grade.key}`, finding.grade) }}
407
+ </UBadge>
408
+ <MarkdownProse
409
+ :text="finding.body"
410
+ class="min-w-0 text-[12px] leading-relaxed text-slate-300"
411
+ />
412
+ </li>
413
+ </ul>
315
414
  </li>
316
415
  </ol>
317
416
  <p v-if="companionVerdicts.length > 1" class="mt-1 text-[11px] text-slate-500">
@@ -148,12 +148,21 @@ const selectedPipeline = computed(() =>
148
148
  // drops the planning presets the engine would refuse. The task-type narrowing already excludes them
149
149
  // for `feature`/`bug`, but NOT for a `spike` / `ralph` / deployment-custom type — and a planning
150
150
  // preset settable as a task's DEFAULT pipeline is a 409 on every later Start.
151
+ // The pipeline this task is ALREADY pinned to is kept in the list even when the narrowing would
152
+ // drop it, the same rule `showOverrideField` states for a hidden override an entity already
153
+ // carries: every one of these gates has been tightened at least once, and a task pinned under the
154
+ // looser one is a legal pre-existing state (a `feature` on a bugfix preset, until the purpose
155
+ // classifier split). Without it the badge below names a pipeline the dropdown does not contain,
156
+ // and the first change to anything else is a one-way door out of a selection the panel is still
157
+ // showing. It is only ever the CURRENT value, so nothing new can be pinned through it.
151
158
  const taskFrame = computed(() => board.serviceOf(props.block))
152
- const selectablePipelines = computed(() =>
153
- pipelines.pipelines.filter((p) =>
159
+ const selectablePipelines = computed(() => {
160
+ const offered = pipelines.pipelines.filter((p) =>
154
161
  pipelineAllowedForManualStart(p, taskFrame.value, board.blocks, props.block.taskType, 'task'),
155
- ),
156
- )
162
+ )
163
+ const pinned = selectedPipeline.value
164
+ return pinned && !offered.some((p) => p.id === pinned.id) ? [pinned, ...offered] : offered
165
+ })
157
166
  function setPipeline(id: string) {
158
167
  board.updateBlock(props.block.id, { pipelineId: id })
159
168
  }
@@ -75,10 +75,10 @@ export const usePipelinesStore = defineStore('pipelines', () => {
75
75
  /** Organizational labels for the pipeline being assembled/edited. */
76
76
  const draftLabels = ref<string[]>([])
77
77
  /**
78
- * The use-case classifier of the pipeline being assembled/edited (`build` / `document` /
79
- * `review` / `research` / `planning`). Drives which task pickers offer the saved pipeline, which
80
- * agent kinds the builder palette shows (a non-`build` purpose hides the Implementation/Testing
81
- * kinds) and which saved pipelines the library lists.
78
+ * The use-case classifier of the pipeline being assembled/edited (`build` / `bugfix` /
79
+ * `document` / `review` / `research` / `planning`). Drives which task pickers offer the saved
80
+ * pipeline, which agent kinds the builder palette shows (a purpose that ships no code hides the
81
+ * Implementation/Testing kinds) and which saved pipelines the library lists.
82
82
  *
83
83
  * Never null: `Pipeline.purpose` is mandatory, so a draft is classified from the moment it
84
84
  * exists. `build` is the default because it is what an unclassified pipeline has always behaved
@@ -6,13 +6,17 @@ import {
6
6
  PIPELINE_PURPOSES,
7
7
  pipelineAllowedForBlockLevel,
8
8
  pipelineAllowedForTaskType,
9
+ pipelineMatchesPurpose,
10
+ pipelineRunsVisualStep,
9
11
  purposeAllowsAgentCategory,
10
12
  purposeSuggestsAgentCategory,
11
13
  purposeSuggestsAgentKind,
14
+ resolveRunServiceScope,
12
15
  } from '@cat-factory/contracts'
13
16
  import type { PipelinePurpose } from '@cat-factory/contracts'
14
17
  import type { Block, Pipeline } from '~/types/domain'
15
18
  import {
19
+ pipelineAllowedForFrame,
16
20
  pipelineAllowedForManualStart,
17
21
  pipelineAllowedForSchedule,
18
22
  pipelineConditionalCount,
@@ -136,6 +140,22 @@ describe('pipelineAllowedForTaskType', () => {
136
140
  }
137
141
  })
138
142
 
143
+ it('offers a bugfix preset to a bug task and withholds it from a feature', () => {
144
+ // "Triage & fix bug" investigates a defect REPORT, triages it with a person and writes a
145
+ // failing reproduction test before anything is fixed. A feature has no report to investigate
146
+ // and nothing red to turn green, so the front half of the run has no input at all. The bug
147
+ // task still gets the whole build ladder beside it — this narrows one preset, not the type.
148
+ const bugfix = pipeline({ purpose: 'bugfix' })
149
+ expect(pipelineAllowedForTaskType(bugfix, 'bug')).toBe(true)
150
+ expect(pipelineAllowedForTaskType(bugfix, 'feature')).toBe(false)
151
+ expect(pipelineAllowedForTaskType(pipeline({ purpose: 'build' }), 'bug')).toBe(true)
152
+ // And it stays out of the task types that demand their own explicit member.
153
+ expect(pipelineAllowedForTaskType(bugfix, 'document')).toBe(false)
154
+ expect(pipelineAllowedForTaskType(bugfix, 'review')).toBe(false)
155
+ // An un-narrowed type (spike, ralph, a deployment's own) is unrestricted as ever.
156
+ expect(pipelineAllowedForTaskType(bugfix, 'spike')).toBe(true)
157
+ })
158
+
139
159
  it('keeps a pipeline whose classifier this build cannot name on a feature / bug task', () => {
140
160
  // The one place this narrowing runs opposite to the document/review one, and it has to. The
141
161
  // value is persisted, so a deployment's own classifier (or one retired since the row was
@@ -189,8 +209,10 @@ describe('pipelineAllowedForBlockLevel (initiative binding)', () => {
189
209
  })
190
210
 
191
211
  describe('purposeAllowsAgentCategory (builder save gate)', () => {
192
- it('a build pipeline, and one whose classifier this build cannot name, may use every category', () => {
193
- for (const purpose of ['build', UNKNOWN_PURPOSE] as const) {
212
+ it('a code-shipping pipeline, and one whose classifier this build cannot name, may use every category', () => {
213
+ // `bugfix` rides with `build` here: the two differ only in the task type they are OFFERED to,
214
+ // and a bug fix designs, implements, tests and merges like any other change.
215
+ for (const purpose of ['build', 'bugfix', UNKNOWN_PURPOSE] as const) {
194
216
  for (const cat of AGENT_CATEGORIES) {
195
217
  expect(purposeAllowsAgentCategory(purpose, cat)).toBe(true)
196
218
  }
@@ -211,8 +233,8 @@ describe('purposeAllowsAgentCategory (builder save gate)', () => {
211
233
  })
212
234
 
213
235
  describe('purposeSuggestsAgentCategory (builder palette filter)', () => {
214
- it('offers the whole catalog to a build pipeline, and to one it cannot name', () => {
215
- for (const purpose of ['build', UNKNOWN_PURPOSE] as const) {
236
+ it('offers the whole catalog to a code-shipping pipeline, and to one it cannot name', () => {
237
+ for (const purpose of ['build', 'bugfix', UNKNOWN_PURPOSE] as const) {
216
238
  for (const cat of AGENT_CATEGORIES) {
217
239
  expect(purposeSuggestsAgentCategory(purpose, cat)).toBe(true)
218
240
  }
@@ -273,6 +295,23 @@ describe('purposeSuggestsAgentKind (what the palette actually filters on)', () =
273
295
  expect(purposeSuggestsAgentCategory('review', 'docs')).toBe(true)
274
296
  })
275
297
 
298
+ it('reads a `build` declaration as covering `bugfix`, but not the other way round', () => {
299
+ // The two purposes are the same WORK, split only so the pickers can withhold a defect-report
300
+ // preset from a feature task. Every kind declaring `build` predates the split, so without
301
+ // this the bugfix palette would open nearly empty — the Bug Investigator included.
302
+ const builder = { category: 'build', purposes: ['build'] } as const
303
+ expect(purposeSuggestsAgentKind('bugfix', builder)).toBe(true)
304
+ expect(purposeSuggestsAgentKind('document', builder)).toBe(false)
305
+ // One-way: naming only `bugfix` claims the defect-report context, not builds in general.
306
+ const bugOnly = { category: 'build', purposes: ['bugfix'] } as const
307
+ expect(purposeSuggestsAgentKind('bugfix', bugOnly)).toBe(true)
308
+ expect(purposeSuggestsAgentKind('build', bugOnly)).toBe(false)
309
+ // ONE relation, not one per surface: the saved-pipeline library reads it through the same
310
+ // helper, so a draft cannot find the build ladder in its palette and not in its library.
311
+ expect(pipelineMatchesPurpose({ purpose: 'build' } as Pipeline, 'bugfix')).toBe(true)
312
+ expect(pipelineMatchesPurpose({ purpose: 'bugfix' } as Pipeline, 'build')).toBe(false)
313
+ })
314
+
276
315
  it('is a declaration, not an exemption from the category', () => {
277
316
  // A kind cannot buy its way back into a purpose its section is not offered to: `docs` is gone
278
317
  // for `planning`, and the palette drops the kind whether or not it named `planning` itself.
@@ -341,6 +380,80 @@ describe('a purpose or category this build does not recognise', () => {
341
380
  })
342
381
  })
343
382
 
383
+ describe('pipelineAllowedForFrame (the visual gate)', () => {
384
+ const serviceFrame = { id: 'blk_svc', level: 'frame', type: 'service' } as Block
385
+ const frontendFrame = { id: 'blk_fe', level: 'frame', type: 'frontend' } as Block
386
+
387
+ // Every build rung ships the CONDITIONAL tester pair: the browser pass declares the frontend
388
+ // scope, the API pass the backend one, so one preset covers both kinds of service.
389
+ const buildRung = pipeline({
390
+ purpose: 'build',
391
+ agentKinds: ['coder', 'tester-api', 'tester-ui', 'merger'],
392
+ stepOptions: [
393
+ null,
394
+ { condition: { serviceScope: 'backend' } },
395
+ { condition: { serviceScope: 'frontend' } },
396
+ null,
397
+ ],
398
+ })
399
+
400
+ it('offers a build rung on a plain backend service — its UI pass is scoped out', () => {
401
+ // The regression: reading "does the pipeline LIST a visual step" hid Standard / Simple /
402
+ // Adaptive / Complex build from the pickers on every non-frontend service, while the engine
403
+ // would have started any of them (it drops the condition-excluded steps before its own gate).
404
+ expect(pipelineAllowedForFrame(buildRung, serviceFrame, [serviceFrame])).toBe(true)
405
+ expect(pipelineAllowedForManualStart(buildRung, serviceFrame, [serviceFrame], 'feature')).toBe(
406
+ true,
407
+ )
408
+ })
409
+
410
+ it('offers it on a frontend frame too, where the UI pass is exactly what runs', () => {
411
+ // Stated as the premise, not just the answer: on this frame the condition ADMITS the browser
412
+ // pass, so the gate is passing on the frame half rather than on the step being scoped out.
413
+ // Without it the case reads identically against the pre-fix implementation, which reached the
414
+ // same `true` down the other branch and asserted nothing about the change.
415
+ expect(pipelineRunsVisualStep(buildRung, resolveRunServiceScope([frontendFrame]))).toBe(true)
416
+ expect(pipelineAllowedForFrame(buildRung, frontendFrame, [frontendFrame])).toBe(true)
417
+ })
418
+
419
+ it('offers everything on a service frame a FRONTEND binds as its backend', () => {
420
+ // The other half of `frameAllowsVisualPipeline`, and the branch with the widest reach: the
421
+ // linked frontend is the UI a change to this service is validated through, so the frame has a
422
+ // UI and even an unconditional visual pipeline may run on it.
423
+ const boundService = { id: 'blk_api', level: 'frame', type: 'service' } as Block
424
+ const binder = {
425
+ id: 'blk_fe',
426
+ level: 'frame',
427
+ type: 'frontend',
428
+ frontendConfig: {
429
+ backendBindings: [
430
+ { envVar: 'API_URL', source: { kind: 'service', serviceBlockId: 'blk_api' } },
431
+ ],
432
+ },
433
+ } as Block
434
+ const blocks = [boundService, binder]
435
+ const visual = pipeline({ purpose: 'build', agentKinds: ['coder', 'visual-confirmation'] })
436
+ expect(pipelineAllowedForFrame(buildRung, boundService, blocks)).toBe(true)
437
+ expect(pipelineAllowedForFrame(visual, boundService, blocks)).toBe(true)
438
+ // ...and it is the BINDING that does it, not the mere presence of a frontend on the board.
439
+ expect(pipelineAllowedForFrame(visual, boundService, [boundService, frontendFrame])).toBe(false)
440
+ })
441
+
442
+ it('still hides a pipeline whose visual step is UNCONDITIONAL on a frame with no UI', () => {
443
+ // The gate the conditional pair did not repeal: a `visual-confirmation` step with nothing to
444
+ // scope it out has no app to show a person, and the engine answers a 409.
445
+ const visual = pipeline({ purpose: 'build', agentKinds: ['coder', 'visual-confirmation'] })
446
+ expect(pipelineAllowedForFrame(visual, serviceFrame, [serviceFrame])).toBe(false)
447
+ expect(pipelineAllowedForFrame(visual, frontendFrame, [frontendFrame])).toBe(true)
448
+ })
449
+
450
+ it('hides a conditional visual pipeline when the frame cannot be resolved', () => {
451
+ // No frame ⇒ no scope to judge the condition against, so the step counts and the frame half
452
+ // refuses. The fail-safe direction, and the same answer the engine gives.
453
+ expect(pipelineAllowedForFrame(buildRung, undefined, [])).toBe(false)
454
+ })
455
+ })
456
+
344
457
  describe('pipelineAllowedForManualStart composes the task-type gate', () => {
345
458
  const noFrame = undefined
346
459
  const blocks: Block[] = []
@@ -3,6 +3,8 @@ import {
3
3
  pipelineAllowedForBlockLevel,
4
4
  pipelineAllowedForTaskType,
5
5
  pipelineHasVisualStep,
6
+ pipelineRunsVisualStep,
7
+ resolveRunServiceScope,
6
8
  } from '@cat-factory/contracts'
7
9
  import type { AgentKind, Block, BlockLevel, Pipeline } from '~/types/domain'
8
10
 
@@ -102,16 +104,40 @@ export { pipelineAllowedForBlockLevel, pipelineAllowedForTaskType }
102
104
  // the gate.
103
105
 
104
106
  /**
105
- * Whether `pipeline` may run on a task under `frame`. A non-visual pipeline is always allowed;
106
- * a visual one only when the frame has a UI (see {@link frameAllowsVisualPipeline}). `blocks` is
107
- * the board's block list, used to find frontend→service links.
107
+ * Whether `pipeline` may run on a task under `frame`. Allowed when the frame has a UI (see
108
+ * {@link frameAllowsVisualPipeline}), and otherwise only when no visual step would REACH a
109
+ * dispatch there ({@link pipelineRunsVisualStep}). `blocks` is the board's block list, used to
110
+ * find frontend→service links.
111
+ *
112
+ * The two halves mirror run admission, which filters the chain through the run conditions and
113
+ * then gates the survivors on the frame. Asking only "does it list a visual step" hid every
114
+ * build rung from every picker on every non-frontend service the moment the ladder adopted the
115
+ * conditional tester pair: each rung lists a `tester-ui` scoped to `frontend`, which a backend
116
+ * task skips.
117
+ *
118
+ * The coarse {@link pipelineHasVisualStep} runs FIRST and is the only work an ordinary pipeline
119
+ * costs: this is called once per row of every pipeline picker, recomputed on each task-type change
120
+ * and each live board push, and the other two halves each walk the whole block list or allocate a
121
+ * scope. A pipeline listing no visual step at all can reach neither answer, so nothing is decided
122
+ * before it that it could decide differently.
123
+ *
124
+ * The scope is derived from the FRAME alone, where the engine also folds in the task's involved
125
+ * services. That cannot disagree here: an involved service is always a non-frontend (only a
126
+ * `service` frame survives `validInvolvedServiceFrames`), so it can only ever re-confirm the
127
+ * `backend` half this frame already sets — and on a frontend frame the gate passes on the frame
128
+ * itself. `runServiceScope.test.ts` pins that filter as the invariant this omission rests on, so
129
+ * widening it fails there rather than here, where the symptom would be a rung this offers and the
130
+ * run-start gate then refuses. An unresolved frame yields an empty scope, which admits every
131
+ * condition and is then refused by the frame half, exactly as the engine refuses it.
108
132
  */
109
133
  export function pipelineAllowedForFrame(
110
134
  pipeline: Pipeline,
111
135
  frame: Block | undefined,
112
136
  blocks: readonly Block[],
113
137
  ): boolean {
114
- return !pipelineHasVisualStep(pipeline) || frameAllowsVisualPipeline(frame, blocks)
138
+ if (!pipelineHasVisualStep(pipeline)) return true
139
+ if (frameAllowsVisualPipeline(frame, blocks)) return true
140
+ return !pipelineRunsVisualStep(pipeline, resolveRunServiceScope(frame ? [frame] : []))
115
141
  }
116
142
 
117
143
  // Launch-availability filters, the surface counterpart to the backend's start-origin gate (a
@@ -126,7 +152,8 @@ export function pipelineAllowedForFrame(
126
152
  * - `'recurring'`-only pipelines the backend would refuse;
127
153
  * - visual pipelines on a frame with no UI;
128
154
  * - pipelines whose `purpose` doesn't fit the given `taskType` (a `document` task offers only
129
- * document pipelines; a `feature`/`bug` task only build + research ones);
155
+ * document pipelines; a `bug` task build + bugfix + research ones, and a `feature` the same
156
+ * minus the bugfix presets, which have no defect report to work from);
130
157
  * - pipelines whose `purpose` doesn't fit the given `blockLevel` (planning pipelines run only on
131
158
  * an initiative block, and an initiative block runs only those).
132
159
  *
@@ -82,6 +82,22 @@ describe('narrowPipelineLibrary', () => {
82
82
  expect(shown.archivedInScope).toBe(1)
83
83
  })
84
84
 
85
+ it('lists a build pipeline in a BUGFIX library, but not the reverse', () => {
86
+ // The one relation `classifierSuits` admits, read here through the same helper the palette
87
+ // reads: a bugfix draft is doing build work, so the whole ladder belongs in front of it, and
88
+ // exact matching would open that library holding the two bugfix presets alone, the same
89
+ // near-empty catalog the palette rule exists to prevent. A build draft is NOT doing defect
90
+ // work, so the bugfix rows stay behind the dial, counted and one click away.
91
+ const lib = [...LIBRARY, row('bugfix-a', 'bugfix')]
92
+ expect(narrowPipelineLibrary(lib, { purpose: 'bugfix' }).offered.map((p) => p.id)).toEqual([
93
+ 'build-a',
94
+ 'bugfix-a',
95
+ ])
96
+ const atBuild = narrowPipelineLibrary(lib, { purpose: 'build' })
97
+ expect(atBuild.offered.map((p) => p.id)).toEqual(['build-a'])
98
+ expect(atBuild.hiddenByPurpose).toBe(4)
99
+ })
100
+
85
101
  it('narrows by neither purpose this build cannot name', () => {
86
102
  // Same disposition as the palette's: an unrecognised value is one this build has nothing to
87
103
  // narrow BY and nothing to narrow it AGAINST, never a reason to guess a current member. A
@@ -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",
@@ -4391,6 +4400,7 @@
4391
4400
  "purposeUnrecognized": "Nicht erkannt ({purpose})",
4392
4401
  "purposeOption": {
4393
4402
  "build": "Entwicklung",
4403
+ "bugfix": "Fehlerbehebung",
4394
4404
  "document": "Dokumentation",
4395
4405
  "review": "Review",
4396
4406
  "research": "Recherche",
@@ -4459,7 +4469,7 @@
4459
4469
  "skillNoneAvailable": "Keine Skills verfügbar. Verknüpfe eine Skill-Quelle in den Kontoeinstellungen.",
4460
4470
  "skillMissing": "Dieser Skill ist nicht mehr im Katalog; wähle einen anderen.",
4461
4471
  "skillNeedsPick": "Ein Skill-Schritt braucht einen ausgewählten Skill, bevor du speichern kannst.",
4462
- "purposeStepsConflict": "Dieser Zweck schreibt keinen Code und führt keine Tests aus, doch die Pipeline enthält noch Implementierungs- oder Testschritte. Entfernen Sie diese oder setzen Sie den Zweck auf Entwicklung.",
4472
+ "purposeStepsConflict": "Dieser Zweck schreibt keinen Code und führt keine Tests aus, doch die Pipeline enthält noch Implementierungs- oder Testschritte. Entfernen Sie diese oder wählen Sie einen Zweck, der Code liefert (Entwicklung oder Fehlerbehebung).",
4463
4473
  "promptEditTooltip": "Systemprompt dieses Agenten bearbeiten",
4464
4474
  "promptEditedTooltip": "Der Systemprompt dieses Agenten ist für diesen Arbeitsbereich bearbeitet",
4465
4475
  "consensusGroups": "Konsensgruppen",
@@ -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",
@@ -4998,6 +5007,7 @@
4998
5007
  },
4999
5008
  "purposeOption": {
5000
5009
  "build": "Build",
5010
+ "bugfix": "Bug fix",
5001
5011
  "document": "Documentation",
5002
5012
  "review": "Review",
5003
5013
  "research": "Research",
@@ -5069,7 +5079,7 @@
5069
5079
  "skillNoneAvailable": "No skills available. Link a skill source in account settings.",
5070
5080
  "skillMissing": "This skill is no longer in the catalog; pick another.",
5071
5081
  "skillNeedsPick": "A Skill step needs a skill selected before you can save.",
5072
- "purposeStepsConflict": "This purpose writes no code and runs no tests, but the pipeline still has implementation or testing steps. Remove them or switch the purpose to Build.",
5082
+ "purposeStepsConflict": "This purpose writes no code and runs no tests, but the pipeline still has implementation or testing steps. Remove them, or switch to a purpose that ships code (Build or Bug fix).",
5073
5083
  "promptEditTooltip": "Edit this agent's system prompt",
5074
5084
  "promptEditedTooltip": "This agent's system prompt is edited for this workspace",
5075
5085
  "consensusGroups": "Consensus groups",
@@ -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",
@@ -4834,6 +4843,7 @@
4834
4843
  "purposeUnrecognized": "No reconocido ({purpose})",
4835
4844
  "purposeOption": {
4836
4845
  "build": "Desarrollo",
4846
+ "bugfix": "Corrección de errores",
4837
4847
  "document": "Documentación",
4838
4848
  "review": "Revisión",
4839
4849
  "research": "Investigación",
@@ -4902,7 +4912,7 @@
4902
4912
  "skillNoneAvailable": "No hay habilidades disponibles. Vincula una fuente de habilidades en la configuración de la cuenta.",
4903
4913
  "skillMissing": "Esta habilidad ya no está en el catálogo; elige otra.",
4904
4914
  "skillNeedsPick": "Un paso Skill necesita una habilidad seleccionada antes de poder guardar.",
4905
- "purposeStepsConflict": "Este propósito no escribe código ni ejecuta pruebas, pero la canalización todavía tiene pasos de implementación o de prueba. Quítalos o cambia el propósito a Desarrollo.",
4915
+ "purposeStepsConflict": "Este propósito no escribe código ni ejecuta pruebas, pero la canalización todavía tiene pasos de implementación o de prueba. Quítalos o cambia a un propósito que entregue código (Desarrollo o Corrección de errores).",
4906
4916
  "promptEditTooltip": "Editar el prompt del sistema de este agente",
4907
4917
  "promptEditedTooltip": "El prompt del sistema de este agente está editado para este espacio de trabajo",
4908
4918
  "consensusGroups": "Grupos de consenso",
@@ -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",
@@ -4834,6 +4843,7 @@
4834
4843
  "purposeUnrecognized": "Non reconnu ({purpose})",
4835
4844
  "purposeOption": {
4836
4845
  "build": "Développement",
4846
+ "bugfix": "Correction de bug",
4837
4847
  "document": "Documentation",
4838
4848
  "review": "Revue",
4839
4849
  "research": "Recherche",
@@ -4902,7 +4912,7 @@
4902
4912
  "skillNoneAvailable": "Aucune compétence disponible. Reliez une source de compétences dans les paramètres du compte.",
4903
4913
  "skillMissing": "Cette compétence n'est plus dans le catalogue ; choisissez-en une autre.",
4904
4914
  "skillNeedsPick": "Une étape Skill a besoin d'une compétence sélectionnée avant de pouvoir enregistrer.",
4905
- "purposeStepsConflict": "Cet objectif n'écrit aucun code et n'exécute aucun test, mais le pipeline contient encore des étapes d'implémentation ou de test. Supprimez-les ou définissez l'objectif sur Développement.",
4915
+ "purposeStepsConflict": "Cet objectif n'écrit aucun code et n'exécute aucun test, mais le pipeline contient encore des étapes d'implémentation ou de test. Supprimez-les ou choisissez un objectif qui livre du code (Développement ou Correction de bug).",
4906
4916
  "promptEditTooltip": "Modifier l'invite système de cet agent",
4907
4917
  "promptEditedTooltip": "L'invite système de cet agent est modifiée pour cet espace de travail",
4908
4918
  "consensusGroups": "Groupes de consensus",
@@ -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": "הסתר ניסיונות תשתית",
@@ -4834,6 +4843,7 @@
4834
4843
  "purposeUnrecognized": "לא מזוהה ({purpose})",
4835
4844
  "purposeOption": {
4836
4845
  "build": "פיתוח",
4846
+ "bugfix": "תיקון באג",
4837
4847
  "document": "תיעוד",
4838
4848
  "review": "סקירה",
4839
4849
  "research": "מחקר",
@@ -4902,7 +4912,7 @@
4902
4912
  "skillNoneAvailable": "אין כישורים זמינים. קישר מקור כישורים בהגדרות החשבון.",
4903
4913
  "skillMissing": "כישור זה כבר אינו בקטלוג; בחר אחר.",
4904
4914
  "skillNeedsPick": "שלב Skill דורש כישור נבחר לפני שתוכל לשמור.",
4905
- "purposeStepsConflict": "מטרה זו אינה כותבת קוד ואינה מריצה בדיקות, אך הצינור עדיין כולל שלבי פיתוח או בדיקה. הסירו אותם או שנו את המטרה לפיתוח.",
4915
+ "purposeStepsConflict": "מטרה זו אינה כותבת קוד ואינה מריצה בדיקות, אך הצינור עדיין כולל שלבי פיתוח או בדיקה. הסירו אותם או בחרו מטרה שמספקת קוד (פיתוח או תיקון באג).",
4906
4916
  "promptEditTooltip": "עריכת הנחיית המערכת של הסוכן הזה",
4907
4917
  "promptEditedTooltip": "הנחיית המערכת של הסוכן הזה נערכה עבור מרחב העבודה הזה",
4908
4918
  "consensusGroups": "קבוצות קונצנזוס",
@@ -2033,6 +2033,13 @@
2033
2033
  "awaitingChoice": "In attesa di una scelta umana",
2034
2034
  "approvalGate": "Gate di approvazione",
2035
2035
  "companionReview": "Revisione companion",
2036
+ "findingSeverity": {
2037
+ "blocker": "Da correggere",
2038
+ "major": "Da sistemare",
2039
+ "minor": "Minore",
2040
+ "ungraded": "Commento",
2041
+ "unrecognized": "Livello non riconosciuto ({level})"
2042
+ },
2036
2043
  "correctionIterations": "{count} iterazione di correzione. | {count} iterazioni di correzione.",
2037
2044
  "state": {
2038
2045
  "pending": "In attesa",
@@ -2069,7 +2076,9 @@
2069
2076
  "closeEsc": "Chiudi (Esc)",
2070
2077
  "companionCapHeading": "{agent} ha raggiunto il suo limite di {attempts} tentativi di rielaborazione, ancora al di sotto della soglia {threshold}.",
2071
2078
  "companionCapDetail": "Esegui un altro ciclo di rielaborazione automatica, procedi al passaggio successivo accettando l'output attuale, oppure arresta e reimposta l'attivita' cosi' da poter modificare gli input e reinviare.",
2072
- "companionStalledHeading": "{agent} ha interrotto il ciclo di rielaborazione dopo {attempts} round su {maxAttempts}: l'ultima revisione e' tornata identica e il punteggio e' rimasto sotto la soglia {threshold}.",
2079
+ "companionCapBlockedHeading": "{agent} ha raggiunto il suo limite di {attempts} tentativi di rielaborazione con {count} rilievo bloccante ancora aperto. | {agent} ha raggiunto il suo limite di {attempts} tentativi di rielaborazione con {count} rilievi bloccanti ancora aperti.",
2080
+ "companionCapBlockedDetail": "Procedere significa accettare il lavoro con quei rilievi irrisolti, per questo la policy di rischio di questa esecuzione non prendera' mai quella decisione al posto tuo. Esegui un altro ciclo di rielaborazione automatica, procedi comunque con l'output attuale, oppure arresta e reimposta l'attivita' cosi' da poter modificare gli input e reinviare.",
2081
+ "companionStalledHeading": "{agent} ha interrotto il ciclo di rielaborazione dopo {attempts} round su {maxAttempts}: l'ultima revisione e' tornata identica e il punteggio non e' cambiato.",
2073
2082
  "companionStalledDetail": "I round rimanenti non sono stati usati perche' avrebbero solo ripetuto un verdetto gia' espresso. Esegui comunque un altro round, procedi al passaggio successivo accettando l'output attuale, oppure arresta e reimposta l'attivita' cosi' da poter modificare gli input e reinviare.",
2074
2083
  "infraAttempts": "Tentativi di infrastruttura",
2075
2084
  "hideInfraAttempts": "Nascondi i tentativi di infrastruttura",
@@ -4391,6 +4400,7 @@
4391
4400
  "purposeUnrecognized": "Non riconosciuto ({purpose})",
4392
4401
  "purposeOption": {
4393
4402
  "build": "Sviluppo",
4403
+ "bugfix": "Correzione di bug",
4394
4404
  "document": "Documentazione",
4395
4405
  "review": "Revisione",
4396
4406
  "research": "Ricerca",
@@ -4459,7 +4469,7 @@
4459
4469
  "skillNoneAvailable": "Nessuna competenza disponibile. Collega una fonte di competenze nelle impostazioni dell'account.",
4460
4470
  "skillMissing": "Questa competenza non è più nel catalogo; scegline un'altra.",
4461
4471
  "skillNeedsPick": "Un passo Skill richiede una competenza selezionata prima di poter salvare.",
4462
- "purposeStepsConflict": "Questo scopo non scrive codice né esegue test, ma la pipeline contiene ancora passaggi di implementazione o di test. Rimuovili o imposta lo scopo su Sviluppo.",
4472
+ "purposeStepsConflict": "Questo scopo non scrive codice né esegue test, ma la pipeline contiene ancora passaggi di implementazione o di test. Rimuovili oppure scegli uno scopo che rilascia codice (Sviluppo o Correzione di bug).",
4463
4473
  "promptEditTooltip": "Modifica il prompt di sistema di questo agente",
4464
4474
  "promptEditedTooltip": "Il prompt di sistema di questo agente è modificato per questo spazio di lavoro",
4465
4475
  "consensusGroups": "Gruppi di consenso",
@@ -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} 回の修正イテレーション。 | {count} 回の修正イテレーション。",
1371
1378
  "state": {
1372
1379
  "pending": "保留中",
@@ -1403,7 +1410,9 @@
1403
1410
  "closeEsc": "閉じる (Esc)",
1404
1411
  "companionCapHeading": "{agent} は {attempts} 回の再作業上限に達しましたが、まだ {threshold} の基準を下回っています。",
1405
1412
  "companionCapDetail": "自動の再作業をもう1回実行するか、現在の出力を受け入れて次のステップに進むか、タスクを停止してリセットし、入力を編集して再送信してください。",
1406
- "companionStalledHeading": "{agent} は {maxAttempts} 回のうち {attempts} 回で再作業ループを停止しました。直前の修正が変更されずに返され、評価も {threshold} の基準を下回ったままでした。",
1413
+ "companionCapBlockedHeading": "{agent} は {attempts} 回の再作業上限に達しましたが、要修正の指摘が {count} 件残っています。 | {agent} は {attempts} 回の再作業上限に達しましたが、要修正の指摘が {count} 件残っています。",
1414
+ "companionCapBlockedDetail": "このまま進めると、これらの指摘が未修正のまま作業を受け入れることになります。そのため、この実行のリスクポリシーがこの選択を代わりに行うことはありません。自動の再作業をもう1回実行するか、現在の出力のまま進めるか、タスクを停止してリセットし、入力を編集して再送信してください。",
1415
+ "companionStalledHeading": "{agent} は {maxAttempts} 回のうち {attempts} 回で再作業ループを停止しました。直前の修正が変更されずに返され、評価も動きませんでした。",
1407
1416
  "companionStalledDetail": "残りの回数は、すでに出された判定を繰り返すだけなので使われていません。それでももう1回実行するか、現在の出力を受け入れて次のステップに進むか、タスクを停止してリセットし、入力を編集して再送信してください。",
1408
1417
  "infraAttempts": "インフラの試行",
1409
1418
  "hideInfraAttempts": "インフラの試行を非表示",
@@ -4834,6 +4843,7 @@
4834
4843
  "purposeUnrecognized": "認識できません ({purpose})",
4835
4844
  "purposeOption": {
4836
4845
  "build": "開発",
4846
+ "bugfix": "バグ修正",
4837
4847
  "document": "ドキュメント",
4838
4848
  "review": "レビュー",
4839
4849
  "research": "調査",
@@ -4902,7 +4912,7 @@
4902
4912
  "skillNoneAvailable": "利用できるスキルがありません。アカウント設定でスキルソースをリンクしてください。",
4903
4913
  "skillMissing": "このスキルはカタログに存在しません。別のスキルを選んでください。",
4904
4914
  "skillNeedsPick": "Skill ステップを保存するにはスキルの選択が必要です。",
4905
- "purposeStepsConflict": "この目的はコードを書かず、テストも実行しませんが、パイプラインには実装またはテストのステップが残っています。それらを削除するか、目的を「開発」に切り替えてください。",
4915
+ "purposeStepsConflict": "この目的はコードを書かず、テストも実行しませんが、パイプラインには実装またはテストのステップが残っています。それらを削除するか、コードを提供する目的(「開発」または「バグ修正」)に切り替えてください。",
4906
4916
  "promptEditTooltip": "このエージェントのシステムプロンプトを編集",
4907
4917
  "promptEditedTooltip": "このエージェントのシステムプロンプトは、このワークスペース用に編集されています",
4908
4918
  "consensusGroups": "コンセンサスグループ",
@@ -1348,6 +1348,13 @@
1348
1348
  "awaitingChoice": "Oczekiwanie na wybór człowieka",
1349
1349
  "approvalGate": "Bramka zatwierdzenia",
1350
1350
  "companionReview": "Recenzja towarzysza",
1351
+ "findingSeverity": {
1352
+ "blocker": "Blokujące",
1353
+ "major": "Do poprawy",
1354
+ "minor": "Drobne",
1355
+ "ungraded": "Komentarz",
1356
+ "unrecognized": "Nierozpoznany poziom ({level})"
1357
+ },
1351
1358
  "correctionIterations": "{count} iteracja korekty. | {count} iteracje korekty. | {count} iteracji korekty.",
1352
1359
  "state": {
1353
1360
  "pending": "Oczekuje",
@@ -1403,7 +1410,9 @@
1403
1410
  "closeEsc": "Zamknij (Esc)",
1404
1411
  "companionCapHeading": "{agent} osiągnął limit {attempts} prób przeróbki, wciąż poniżej progu {threshold}.",
1405
1412
  "companionCapDetail": "Wykonaj jeszcze jedną automatyczną rundę przeróbki, przejdź do następnego kroku akceptując bieżący wynik, albo zatrzymaj i zresetuj zadanie, aby edytować dane wejściowe i przesłać ponownie.",
1406
- "companionStalledHeading": "{agent} zatrzymał pętlę przeróbek po {attempts} z {maxAttempts} rund: ostatnia poprawka wróciła bez zmian, a ocena pozostała poniżej progu {threshold}.",
1413
+ "companionCapBlockedHeading": "{agent} osiągnął limit {attempts} prób przeróbki, a {count} uwaga blokująca pozostaje otwarta. | {agent} osiągnął limit {attempts} prób przeróbki, a {count} uwagi blokujące pozostają otwarte. | {agent} osiągnął limit {attempts} prób przeróbki, a {count} uwag blokujących pozostaje otwartych.",
1414
+ "companionCapBlockedDetail": "Kontynuacja oznacza przyjęcie pracy z tymi nienaprawionymi uwagami, dlatego polityka ryzyka tego przebiegu nigdy nie podejmie takiej decyzji za ciebie. Wykonaj jeszcze jedną automatyczną rundę przeróbki, kontynuuj mimo to z bieżącym wynikiem, albo zatrzymaj i zresetuj zadanie, aby edytować dane wejściowe i przesłać ponownie.",
1415
+ "companionStalledHeading": "{agent} zatrzymał pętlę przeróbek po {attempts} z {maxAttempts} rund: ostatnia poprawka wróciła bez zmian, a ocena się nie zmieniła.",
1407
1416
  "companionStalledDetail": "Pozostałe rundy nie zostały wykorzystane, bo tylko powtórzyłyby już wydaną ocenę. Wykonaj mimo to jeszcze jedną rundę, przejdź do następnego kroku akceptując bieżący wynik, albo zatrzymaj i zresetuj zadanie, aby edytować dane wejściowe i przesłać ponownie.",
1408
1417
  "infraAttempts": "Próby infrastrukturalne",
1409
1418
  "hideInfraAttempts": "Ukryj próby infrastrukturalne",
@@ -4834,6 +4843,7 @@
4834
4843
  "purposeUnrecognized": "Nierozpoznany ({purpose})",
4835
4844
  "purposeOption": {
4836
4845
  "build": "Programowanie",
4846
+ "bugfix": "Poprawka błędu",
4837
4847
  "document": "Dokumentacja",
4838
4848
  "review": "Przegląd",
4839
4849
  "research": "Badania",
@@ -4902,7 +4912,7 @@
4902
4912
  "skillNoneAvailable": "Brak dostępnych umiejętności. Połącz źródło umiejętności w ustawieniach konta.",
4903
4913
  "skillMissing": "Tej umiejętności nie ma już w katalogu; wybierz inną.",
4904
4914
  "skillNeedsPick": "Krok Skill wymaga wybranej umiejętności, zanim będzie można zapisać.",
4905
- "purposeStepsConflict": "To przeznaczenie nie tworzy kodu ani nie uruchamia testów, ale potok nadal zawiera kroki programowania lub testowania. Usuń je lub zmień przeznaczenie na Programowanie.",
4915
+ "purposeStepsConflict": "To przeznaczenie nie tworzy kodu ani nie uruchamia testów, ale potok nadal zawiera kroki programowania lub testowania. Usuń je lub zmień przeznaczenie na takie, które dostarcza kod (Programowanie lub Poprawka błędu).",
4906
4916
  "promptEditTooltip": "Edytuj prompt systemowy tego agenta",
4907
4917
  "promptEditedTooltip": "Prompt systemowy tego agenta jest zmieniony dla tego obszaru roboczego",
4908
4918
  "consensusGroups": "Grupy konsensusu",
@@ -1367,6 +1367,13 @@
1367
1367
  "awaitingChoice": "Bir kullanıcı seçimi bekleniyor",
1368
1368
  "approvalGate": "Onay kapısı",
1369
1369
  "companionReview": "Yardımcı incelemesi",
1370
+ "findingSeverity": {
1371
+ "blocker": "Mutlaka düzeltilmeli",
1372
+ "major": "Düzeltilmeli",
1373
+ "minor": "Küçük",
1374
+ "ungraded": "Yorum",
1375
+ "unrecognized": "Tanınmayan düzey ({level})"
1376
+ },
1370
1377
  "correctionIterations": "{count} düzeltme yinelemesi. | {count} düzeltme yinelemesi.",
1371
1378
  "state": {
1372
1379
  "pending": "Beklemede",
@@ -1403,7 +1410,9 @@
1403
1410
  "closeEsc": "Kapat (Esc)",
1404
1411
  "companionCapHeading": "{agent} {attempts} deneme yeniden çalışma sınırına ulaştı, hâlâ {threshold} barının altında.",
1405
1412
  "companionCapDetail": "Bir otomatik yeniden çalışma turu daha yapın, mevcut çıktıyı kabul ederek sonraki adıma geçin ya da durup görevi sıfırlayarak girdileri düzenleyip yeniden gönderin.",
1406
- "companionStalledHeading": "{agent}, {maxAttempts} turun {attempts} turunda yeniden çalışma döngüsünü durdurdu: son revizyon değişmeden döndü ve puan {threshold} barının altında kaldı.",
1413
+ "companionCapBlockedHeading": "{agent} {attempts} deneme yeniden çalışma sınırına ulaştı, mutlaka düzeltilmesi gereken {count} bulgu hâlâ açık. | {agent} {attempts} deneme yeniden çalışma sınırına ulaştı, mutlaka düzeltilmesi gereken {count} bulgu hâlâ açık.",
1414
+ "companionCapBlockedDetail": "Devam etmek, bu bulgular düzeltilmeden işi kabul etmek demektir; bu yüzden bu çalışmanın risk politikası bu seçimi sizin yerinize asla yapmaz. Bir otomatik yeniden çalışma turu daha yapın, mevcut çıktıyla yine de devam edin ya da durup görevi sıfırlayarak girdileri düzenleyip yeniden gönderin.",
1415
+ "companionStalledHeading": "{agent}, {maxAttempts} turun {attempts} turunda yeniden çalışma döngüsünü durdurdu: son revizyon değişmeden döndü ve puan hiç değişmedi.",
1407
1416
  "companionStalledDetail": "Kalan turlar kullanılmadı, çünkü yalnızca verilmiş bir kararı yineleyeceklerdi. Yine de bir tur daha yapın, mevcut çıktıyı kabul ederek sonraki adıma geçin ya da durup görevi sıfırlayarak girdileri düzenleyip yeniden gönderin.",
1408
1417
  "infraAttempts": "Altyapı denemeleri",
1409
1418
  "hideInfraAttempts": "Altyapı denemelerini gizle",
@@ -4834,6 +4843,7 @@
4834
4843
  "purposeUnrecognized": "Tanınmayan ({purpose})",
4835
4844
  "purposeOption": {
4836
4845
  "build": "Geliştirme",
4846
+ "bugfix": "Hata düzeltme",
4837
4847
  "document": "Dokümantasyon",
4838
4848
  "review": "İnceleme",
4839
4849
  "research": "Araştırma",
@@ -4902,7 +4912,7 @@
4902
4912
  "skillNoneAvailable": "Kullanılabilir beceri yok. Hesap ayarlarında bir beceri kaynağı bağlayın.",
4903
4913
  "skillMissing": "Bu beceri artık katalogda yok; başka birini seçin.",
4904
4914
  "skillNeedsPick": "Bir Skill adımı, kaydedebilmeniz için seçilmiş bir beceri gerektirir.",
4905
- "purposeStepsConflict": "Bu amaç kod yazmaz ve test çalıştırmaz, ancak işlem hattında hâlâ uygulama veya test adımları var. Bunları kaldırın veya amacı Geliştirme olarak değiştirin.",
4915
+ "purposeStepsConflict": "Bu amaç kod yazmaz ve test çalıştırmaz, ancak işlem hattında hâlâ uygulama veya test adımları var. Bunları kaldırın veya kod teslim eden bir amaca geçin (Geliştirme ya da Hata düzeltme).",
4906
4916
  "promptEditTooltip": "Bu ajanın sistem istemini düzenle",
4907
4917
  "promptEditedTooltip": "Bu ajanın sistem istemi bu çalışma alanı için düzenlenmiş",
4908
4918
  "consensusGroups": "Uzlaşı grupları",
@@ -1348,6 +1348,13 @@
1348
1348
  "awaitingChoice": "Очікування вибору людини",
1349
1349
  "approvalGate": "Гейт затвердження",
1350
1350
  "companionReview": "Огляд компаньйона",
1351
+ "findingSeverity": {
1352
+ "blocker": "Блокувальне",
1353
+ "major": "Треба виправити",
1354
+ "minor": "Незначне",
1355
+ "ungraded": "Коментар",
1356
+ "unrecognized": "Нерозпізнаний рівень ({level})"
1357
+ },
1351
1358
  "correctionIterations": "{count} ітерація виправлення. | {count} ітерації виправлення. | {count} ітерацій виправлення.",
1352
1359
  "state": {
1353
1360
  "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} спроб переробки, {count} блокувальне зауваження досі відкрите. | {agent} досяг ліміту в {attempts} спроб переробки, {count} блокувальні зауваження досі відкриті. | {agent} досяг ліміту в {attempts} спроб переробки, {count} блокувальних зауважень досі відкриті.",
1414
+ "companionCapBlockedDetail": "Продовження означає прийняття роботи з невиправленими зауваженнями, тому політика ризику цього запуску ніколи не зробить цей вибір за вас. Виконайте ще один автоматичний раунд переробки, продовжте попри це з поточним виводом, або зупиніться та скиньте завдання, щоб відредагувати вхідні дані й надіслати повторно.",
1415
+ "companionStalledHeading": "{agent} зупинив цикл переробки після {attempts} з {maxAttempts} раундів: остання версія повернулася без змін, а оцінка не змінилася.",
1407
1416
  "companionStalledDetail": "Решта раундів залишилися невикористаними, бо лише повторили б уже винесений висновок. Виконайте ще один раунд усе одно, перейдіть до наступного кроку, прийнявши поточний вивід, або зупиніться та скиньте завдання, щоб відредагувати вхідні дані й надіслати повторно.",
1408
1417
  "infraAttempts": "Спроби інфраструктури",
1409
1418
  "hideInfraAttempts": "Приховати спроби інфраструктури",
@@ -4834,6 +4843,7 @@
4834
4843
  "purposeUnrecognized": "Нерозпізнано ({purpose})",
4835
4844
  "purposeOption": {
4836
4845
  "build": "Розробка",
4846
+ "bugfix": "Виправлення помилки",
4837
4847
  "document": "Документація",
4838
4848
  "review": "Рецензування",
4839
4849
  "research": "Дослідження",
@@ -4902,7 +4912,7 @@
4902
4912
  "skillNoneAvailable": "Немає доступних навичок. Пов'яжіть джерело навичок у налаштуваннях облікового запису.",
4903
4913
  "skillMissing": "Цієї навички більше немає в каталозі; виберіть іншу.",
4904
4914
  "skillNeedsPick": "Крок Skill потребує вибраної навички, перш ніж ви зможете зберегти.",
4905
- "purposeStepsConflict": "Це призначення не пише код і не запускає тести, але конвеєр досі містить кроки програмування або тестування. Видаліть їх або змініть призначення на Розробку.",
4915
+ "purposeStepsConflict": "Це призначення не пише код і не запускає тести, але конвеєр досі містить кроки програмування або тестування. Видаліть їх або змініть призначення на таке, що постачає код (Розробка або Виправлення помилки).",
4906
4916
  "promptEditTooltip": "Редагувати системний промпт цього агента",
4907
4917
  "promptEditedTooltip": "Системний промпт цього агента змінено для цього робочого простору",
4908
4918
  "consensusGroups": "Групи консенсусу",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cat-factory/app",
3
- "version": "0.270.2",
3
+ "version": "0.271.0",
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",
@@ -40,7 +40,7 @@
40
40
  "valibot": "^1.4.2",
41
41
  "vue": "3.5.41",
42
42
  "wretch": "^3.0.9",
43
- "@cat-factory/contracts": "0.308.1"
43
+ "@cat-factory/contracts": "0.310.0"
44
44
  },
45
45
  "devDependencies": {
46
46
  "@toad-contracts/testing": "0.3.2",