@cat-factory/app 0.141.2 → 0.143.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.
@@ -1,6 +1,7 @@
1
1
  <script setup lang="ts">
2
2
  // Create a new task on the board. The user names the task and writes its
3
- // description themselves there are no auto-generated placeholder titles. The
3
+ // description themselves (a REVIEW task is the one exception — its title is optional
4
+ // and derived from the target PR when left blank, since the PR is the subject). The
4
5
  // task lands in `planned` state; it is never launched here. The user starts a
5
6
  // pipeline on it explicitly (and can keep editing it until they do).
6
7
  //
@@ -19,10 +20,12 @@ import type {
19
20
  TaskTypeFields,
20
21
  } from '~/types/domain'
21
22
  import { DOC_KINDS, DOC_KIND_FIELDS } from '~/types/domain'
23
+ import type { DropdownMenuItem } from '@nuxt/ui'
22
24
  import ContextDocumentPicker from '~/components/documents/ContextDocumentPicker.vue'
23
25
  import ContextIssuePicker from '~/components/tasks/ContextIssuePicker.vue'
24
26
  import { riskPolicyOptionLabel, riskPolicySummary } from '~/utils/riskPolicy'
25
27
  import { pipelineAllowedForManualStart } from '~/utils/pipeline'
28
+ import { buildFragmentPickerGroups } from '~/utils/fragmentPicker'
26
29
 
27
30
  const ui = useUiStore()
28
31
  const board = useBoardStore()
@@ -32,6 +35,8 @@ const riskPolicies = useRiskPoliciesStore()
32
35
  const modelPresets = useModelPresetsStore()
33
36
  const pipelines = usePipelinesStore()
34
37
  const agentConfig = useAgentConfigStore()
38
+ const fragments = useFragmentsStore()
39
+ const accounts = useAccountsStore()
35
40
  const toast = useToast()
36
41
  const { t } = useI18n()
37
42
 
@@ -121,6 +126,12 @@ const docOutlineHints = ref('')
121
126
  const reviewPrRef = ref('')
122
127
  const reviewFocus = ref('')
123
128
 
129
+ // Best-practice prompt fragments the user pins on the task up front (folded into its agents
130
+ // on top of the service-level standards, exactly like the inspector's picker). Chosen from the
131
+ // resolved catalog, filtered to the enclosing frame's block type ("appropriate scope").
132
+ const fragmentIds = ref<string[]>([])
133
+ const isReview = computed(() => taskType.value === 'review')
134
+
124
135
  // Parse the PR-reference input into the contract fields: a bare positive integer (optionally
125
136
  // `#`-prefixed) becomes `prNumber` (a PR on the service's linked repo); anything else is taken
126
137
  // as a full URL (`prUrl`). Returns undefined when blank or unparseable — the caller uses that
@@ -135,6 +146,21 @@ function parseReviewPrRef(raw: string): Pick<TaskTypeFields, 'prUrl' | 'prNumber
135
146
  }
136
147
  return { prUrl: trimmed }
137
148
  }
149
+
150
+ // A review task doesn't require a title (the PR reference IS the subject), so when the user
151
+ // leaves it blank we derive a concise one from the parsed PR ref — `owner/repo#123` from a
152
+ // GitHub-style URL, else `#number`, else a bare label — so the board card still reads sensibly.
153
+ function deriveReviewTitle(raw: string): string {
154
+ const parsed = parseReviewPrRef(raw)
155
+ if (parsed?.prNumber)
156
+ return t('board.addTask.review.derivedTitle', { ref: `#${parsed.prNumber}` })
157
+ if (parsed?.prUrl) {
158
+ const m = /github\.com\/([^/]+)\/([^/]+)\/pull\/(\d+)/.exec(parsed.prUrl)
159
+ const refLabel = m ? `${m[1]}/${m[2]}#${m[3]}` : parsed.prUrl
160
+ return t('board.addTask.review.derivedTitle', { ref: refLabel })
161
+ }
162
+ return t('board.addTask.review.derivedTitleFallback')
163
+ }
138
164
  // Per-kind specific fields (see DOC_KIND_FIELDS). Held in one keyed record; only the fields
139
165
  // for the selected kind are shown and submitted, so a value from a previously-selected kind is
140
166
  // never sent. The catalog keys below keep the labels/placeholders i18n and drift-guarded.
@@ -286,11 +312,58 @@ const selectedModelPresetLabel = computed(() => {
286
312
  )
287
313
  })
288
314
 
315
+ // ---- best-practice prompt fragments (pinned at creation) -------------------
316
+ // The fragments already chosen, resolved against the catalog. An id the catalog no longer
317
+ // resolves still renders (labelled by its raw id) so it stays visible and removable — mirrors
318
+ // the inspector's TaskStructure picker.
319
+ const selectedFragments = computed(() =>
320
+ fragmentIds.value.map((id) => fragments.getFragment(id) ?? { id, title: id, summary: '' }),
321
+ )
322
+ // A trailing group linking out to the fragment library (board tier always; account tier when
323
+ // enabled) — managing fragments is open to every member, exactly like the inspector picker.
324
+ const fragmentManageItems = computed<DropdownMenuItem[]>(() => {
325
+ const items: DropdownMenuItem[] = [
326
+ {
327
+ label: t('inspector.fragments.manageBoard'),
328
+ icon: 'i-lucide-book-marked',
329
+ onSelect: () => ui.openFragmentLibrary(),
330
+ },
331
+ ]
332
+ if (accounts.enabled) {
333
+ items.push({
334
+ label: t('inspector.fragments.manageAccount'),
335
+ icon: 'i-lucide-users',
336
+ onSelect: () => ui.openAccountSettings('fragments'),
337
+ })
338
+ }
339
+ return items
340
+ })
341
+ // Picker menu: fragments appropriate to the enclosing frame's block type (the "scope"), not
342
+ // already selected, grouped by category, with the management links appended as the final group.
343
+ // Falls back to `service` before a frame resolves so the catalog is still browsable.
344
+ const fragmentMenu = computed<DropdownMenuItem[][]>(() => {
345
+ const selected = new Set(fragmentIds.value)
346
+ return [
347
+ ...buildFragmentPickerGroups(
348
+ fragments.forBlockType(frame.value?.type ?? 'service'),
349
+ (id) => selected.has(id),
350
+ (id) => {
351
+ if (!fragmentIds.value.includes(id)) fragmentIds.value = [...fragmentIds.value, id]
352
+ },
353
+ ),
354
+ fragmentManageItems.value,
355
+ ]
356
+ })
357
+ function removeFragment(id: string) {
358
+ fragmentIds.value = fragmentIds.value.filter((x) => x !== id)
359
+ }
360
+
289
361
  // Hide UI-testing pipelines (`tester-ui` / `visual-confirmation`) when the target frame has no
290
362
  // UI to exercise — they'd be refused server-side (see utils/pipeline + the backend gate). Also
291
363
  // hide `'recurring'`-only pipelines (a one-off task start of one is refused at run start) and,
292
- // for a `document` task, every non-document pipeline (it authors a doc only document pipelines
293
- // are relevant, per the `purpose` classifier). Re-filters as the chosen task type changes.
364
+ // for a `document` / `review` task, every pipeline whose purpose doesn't match (a doc task authors
365
+ // a doc, a review task reviews a PR only document / review pipelines are relevant, per the
366
+ // `purpose` classifier). Re-filters as the chosen task type changes.
294
367
  const selectablePipelines = computed(() =>
295
368
  pipelines.pipelines.filter((p) =>
296
369
  pipelineAllowedForManualStart(p, frame.value, board.blocks, taskType.value),
@@ -302,14 +375,16 @@ const selectablePipelines = computed(() =>
302
375
  // must appear in the form BEFORE creation:
303
376
  // - `ralph` needs its preset so the per-task validation command + iteration budget the `ralph`
304
377
  // agent contributes surface for editing ("choose at run time" would be a dead end);
305
- // - a `document` task defaults to `pl_document` so its document-only picker (the `purpose` gate
306
- // hides every non-document pipeline) is never rendered empty.
307
- // The other typed defaults (spike/review) carry no up-front config and don't narrow their picker,
308
- // so the modal leaves `pipelineId` unset and `BoardService` applies the backend type-default at
309
- // creation. Keep these ids in step with the backend helper.
378
+ // - a `document` task defaults to `pl_document` and a `review` task to `pl_review` so their
379
+ // purpose-narrowed picker (the `purpose` gate hides every non-document / non-review pipeline)
380
+ // is never rendered empty.
381
+ // The other typed default (spike) carries no up-front config and doesn't narrow its picker, so the
382
+ // modal leaves `pipelineId` unset and `BoardService` applies the backend type-default at creation.
383
+ // Keep these ids in step with the backend helper.
310
384
  const DEFAULT_PIPELINE_FOR_TYPE: Partial<Record<TaskTypeChoice, string>> = {
311
385
  ralph: 'pl_ralph',
312
386
  document: 'pl_document',
387
+ review: 'pl_review',
313
388
  }
314
389
  watch(taskType, (next) => {
315
390
  const preset = DEFAULT_PIPELINE_FOR_TYPE[next]
@@ -468,6 +543,7 @@ watch(open, (isOpen) => {
468
543
  docOutlineHints.value = ''
469
544
  reviewPrRef.value = ''
470
545
  reviewFocus.value = ''
546
+ fragmentIds.value = []
471
547
  for (const key of Object.keys(docKindFieldValues) as DocKindFieldKey[])
472
548
  delete docKindFieldValues[key]
473
549
  riskPolicyId.value = ''
@@ -491,6 +567,8 @@ watch(open, (isOpen) => {
491
567
  }
492
568
  documents.loadDocuments().catch(() => {})
493
569
  tasks.loadTasks().catch(() => {})
570
+ // Load the best-practice fragment catalog so the picker is populated (no-op while current).
571
+ fragments.ensureLoaded().catch(() => {})
494
572
  // Fetch any staged search-hit issue's body so its description shows read-only below.
495
573
  resolvePendingIssueBodies().catch(() => {})
496
574
  })
@@ -523,6 +601,7 @@ const { requestClose } = useUnsavedGuard({
523
601
  modelPresetId: modelPresetId.value,
524
602
  pipelineId: pipelineId.value,
525
603
  agentConfig: { ...agentConfigValues.value },
604
+ fragmentIds: [...fragmentIds.value],
526
605
  context: pendingContext.value.map(contextKey),
527
606
  }),
528
607
  })
@@ -536,8 +615,10 @@ const RALPH_VALIDATION_COMMAND_ID = 'ralph.validationCommand'
536
615
 
537
616
  const canAdd = computed(() => {
538
617
  if (isRecurring.value) return recurringFrameId.value !== null
618
+ // A review task doesn't require a title (the PR reference is the subject — we derive one),
619
+ // so it only needs a valid target PR. Every other type still requires a title.
620
+ if (isReview.value) return parseReviewPrRef(reviewPrRef.value) !== undefined
539
621
  if (title.value.trim().length === 0) return false
540
- if (taskType.value === 'review' && !parseReviewPrRef(reviewPrRef.value)) return false
541
622
  if (
542
623
  taskType.value === 'ralph' &&
543
624
  configValue(RALPH_VALIDATION_COMMAND_ID, '').trim().length === 0
@@ -568,15 +649,21 @@ async function add() {
568
649
  const fullDescription =
569
650
  [...linkedIssueBodies.value.map((b) => b.body), notes].filter(Boolean).join('\n\n') ||
570
651
  undefined
571
- const block = await board.addTask(containerId, title.value.trim(), fullDescription, {
652
+ // A review task's title is optional; when blank we derive one from the PR reference so the
653
+ // board card still reads sensibly (the backend also folds the PR ref into the description).
654
+ const effectiveTitle =
655
+ title.value.trim() || (isReview.value ? deriveReviewTitle(reviewPrRef.value) : '')
656
+ const block = await board.addTask(containerId, effectiveTitle, fullDescription, {
572
657
  taskType: taskType.value as CreateTaskType,
573
658
  ...(typeFields ? { taskTypeFields: typeFields } : {}),
574
- ...(riskPolicyId.value ? { riskPolicyId: riskPolicyId.value } : {}),
659
+ // A review task merges nothing, so its risk (merge) policy is meaningless never send it.
660
+ ...(riskPolicyId.value && !isReview.value ? { riskPolicyId: riskPolicyId.value } : {}),
575
661
  ...(modelPresetId.value ? { modelPresetId: modelPresetId.value } : {}),
576
662
  ...(pipelineId.value ? { pipelineId: pipelineId.value } : {}),
577
663
  ...(Object.keys(agentConfigValues.value).length
578
664
  ? { agentConfig: agentConfigValues.value }
579
665
  : {}),
666
+ ...(fragmentIds.value.length ? { fragmentIds: [...fragmentIds.value] } : {}),
580
667
  ...(technical.value ? { technical: true } : {}),
581
668
  })
582
669
  if (block) {
@@ -645,11 +732,19 @@ async function add() {
645
732
  </div>
646
733
 
647
734
  <template v-if="!isRecurring">
648
- <UFormField :label="t('board.addTask.titleField')" required>
735
+ <UFormField
736
+ :label="t('board.addTask.titleField')"
737
+ :required="!isReview"
738
+ :hint="isReview ? t('board.addTask.optional') : undefined"
739
+ >
649
740
  <UInput
650
741
  v-model="title"
651
742
  data-testid="add-task-title"
652
- :placeholder="t('board.addTask.titlePlaceholder')"
743
+ :placeholder="
744
+ isReview
745
+ ? t('board.addTask.titlePlaceholderReview')
746
+ : t('board.addTask.titlePlaceholder')
747
+ "
653
748
  autofocus
654
749
  class="w-full"
655
750
  @keydown.enter="add"
@@ -902,7 +997,8 @@ async function add() {
902
997
  />
903
998
  </UFormField>
904
999
 
905
- <UFormField :label="t('board.addTask.mergePolicy')">
1000
+ <!-- A review task merges nothing, so its risk (merge) policy is meaningless — omit it. -->
1001
+ <UFormField v-if="!isReview" :label="t('board.addTask.mergePolicy')">
906
1002
  <UDropdownMenu :items="presetMenu" class="w-full">
907
1003
  <UButton
908
1004
  color="neutral"
@@ -964,6 +1060,43 @@ async function add() {
964
1060
  </div>
965
1061
  </div>
966
1062
 
1063
+ <!-- Best-practice fragments pinned on the task at creation, scoped to the frame's type. -->
1064
+ <div class="space-y-2">
1065
+ <div class="flex items-center justify-between">
1066
+ <span class="text-[11px] font-semibold uppercase tracking-wide text-slate-400">
1067
+ {{ t('board.addTask.bestPractices') }}
1068
+ </span>
1069
+ <UDropdownMenu :items="fragmentMenu">
1070
+ <UButton
1071
+ color="neutral"
1072
+ variant="soft"
1073
+ size="xs"
1074
+ icon="i-lucide-plus"
1075
+ trailing-icon="i-lucide-chevron-down"
1076
+ >
1077
+ {{ t('board.addTask.attach') }}
1078
+ </UButton>
1079
+ </UDropdownMenu>
1080
+ </div>
1081
+ <div v-if="selectedFragments.length" class="flex flex-wrap gap-1">
1082
+ <UBadge
1083
+ v-for="f in selectedFragments"
1084
+ :key="f.id"
1085
+ color="primary"
1086
+ variant="subtle"
1087
+ size="sm"
1088
+ class="cursor-pointer"
1089
+ :title="f.summary"
1090
+ @click="removeFragment(f.id)"
1091
+ >
1092
+ {{ f.title }}<UIcon name="i-lucide-x" class="ms-0.5 h-3 w-3" />
1093
+ </UBadge>
1094
+ </div>
1095
+ <p v-else class="text-[11px] text-slate-500">
1096
+ {{ t('board.addTask.bestPracticesHint') }}
1097
+ </p>
1098
+ </div>
1099
+
967
1100
  <!-- Context documents (ungated; Attach disabled until a source is connected). -->
968
1101
  <div class="space-y-2">
969
1102
  <div class="flex items-center justify-between">
@@ -282,7 +282,7 @@ async function linkDocumentFragment() {
282
282
  // error surfaced) so a retry re-attempts just those, never re-linking what already went in.
283
283
  let linked = 0
284
284
  let firstError: unknown
285
- for (const { path, ref } of [...stagedDocRefs.value]) {
285
+ for (const { path, ref } of stagedDocRefs.value) {
286
286
  try {
287
287
  await library.createDocumentFragment({ source, ref, tags })
288
288
  const i = docFilePaths.value.indexOf(path)
@@ -1,13 +1,16 @@
1
1
  <script setup lang="ts">
2
2
  // Startup advisory for unhealthy pipelines. Opened once per session from the board page when
3
3
  // `usePipelineHealth` reports any issue. Lists:
4
+ // • new built-in pipelines the workspace doesn't have yet (ADD them);
4
5
  // • invalid pipelines (unknown agent kind / bad shape) — DELETE a custom one, RESEED a built-in;
5
6
  // • outdated built-ins (a newer catalog definition is available) — RESEED to adopt it.
6
- // Detection is client-side (see usePipelineHealth); the actions hit the pipelines store.
7
+ // Adding a new built-in and reseeding an existing one are the same reseed call (it creates or
8
+ // updates by catalog id). Detection is client-side (see usePipelineHealth); the actions hit the
9
+ // pipelines store.
7
10
  const { t } = useI18n()
8
11
  const ui = useUiStore()
9
12
  const pipelines = usePipelinesStore()
10
- const { invalid, outdated, hasIssues } = usePipelineHealth()
13
+ const { invalid, outdated, newPipelines, hasIssues } = usePipelineHealth()
11
14
  const toast = useToast()
12
15
 
13
16
  const open = computed({
@@ -45,17 +48,20 @@ const reseed = (id: string) =>
45
48
  const remove = (id: string) =>
46
49
  run(id, () => pipelines.removePipeline(id), t('pipeline.health.toast.deleteFailed'))
47
50
 
48
- /** Reseed every reseedable pipeline (outdated built-ins + invalid built-ins) in one go. */
51
+ /** Reseed every reseedable pipeline (new + outdated built-ins + invalid built-ins) in one go. */
49
52
  async function reseedAll() {
50
- const ids = [...invalid.value.filter((h) => h.pipeline.builtin), ...outdated.value].map(
51
- (h) => h.pipeline.id,
52
- )
53
+ const ids = [
54
+ ...newPipelines.value.map((p) => p.id),
55
+ ...invalid.value.filter((h) => h.pipeline.builtin).map((h) => h.pipeline.id),
56
+ ...outdated.value.map((h) => h.pipeline.id),
57
+ ]
53
58
  for (const id of new Set(ids)) await reseed(id)
54
59
  }
55
60
 
56
61
  const reseedableCount = computed(
57
62
  () =>
58
63
  new Set([
64
+ ...newPipelines.value.map((p) => p.id),
59
65
  ...invalid.value.filter((h) => h.pipeline.builtin).map((h) => h.pipeline.id),
60
66
  ...outdated.value.map((h) => h.pipeline.id),
61
67
  ]).size,
@@ -71,6 +77,41 @@ const reseedableCount = computed(
71
77
  </div>
72
78
 
73
79
  <div v-else class="space-y-5">
80
+ <!-- New built-in pipelines the workspace can add. -->
81
+ <section v-if="newPipelines.length" class="space-y-2">
82
+ <div class="flex items-center gap-2">
83
+ <UIcon name="i-lucide-sparkles" class="h-4 w-4 text-emerald-400" />
84
+ <h3 class="text-sm font-semibold text-slate-200">
85
+ {{ t('pipeline.health.newHeading') }}
86
+ </h3>
87
+ </div>
88
+ <p class="text-[11px] text-slate-500">{{ t('pipeline.health.newDescription') }}</p>
89
+ <ul class="space-y-2">
90
+ <li
91
+ v-for="p in newPipelines"
92
+ :key="p.id"
93
+ class="flex items-center justify-between gap-3 rounded-lg border border-slate-800 bg-slate-900/40 p-3"
94
+ >
95
+ <div class="min-w-0">
96
+ <span class="truncate text-sm font-medium text-slate-100 capitalize">{{
97
+ p.name
98
+ }}</span>
99
+ </div>
100
+ <UButton
101
+ size="xs"
102
+ color="primary"
103
+ variant="subtle"
104
+ icon="i-lucide-plus"
105
+ :loading="isBusy(p.id)"
106
+ :disabled="anyBusy"
107
+ @click="reseed(p.id)"
108
+ >
109
+ {{ t('pipeline.health.add') }}
110
+ </UButton>
111
+ </li>
112
+ </ul>
113
+ </section>
114
+
74
115
  <!-- Invalid: unknown agent kinds or a broken shape. -->
75
116
  <section v-if="invalid.length" class="space-y-2">
76
117
  <div class="flex items-center gap-2">
@@ -142,4 +142,26 @@ describe('usePipelineHealth', () => {
142
142
  expect(invalid.value).toHaveLength(1)
143
143
  expect(outdated.value).toHaveLength(0)
144
144
  })
145
+
146
+ it('surfaces a brand-new built-in the workspace does not have yet (offer to add)', () => {
147
+ // A board seeded before `pl_review` shipped: the catalog versions advertise it, but no stored
148
+ // pipeline has that id — so it must appear as a "new" pipeline the user can add.
149
+ const stored = builtin(['coder', 'reviewer'], { id: 'pl_full', version: 1 })
150
+ const { newPipelines, hasIssues, invalid, outdated } = scan([stored], {
151
+ pl_full: 1,
152
+ pl_review: 4,
153
+ })
154
+ expect(newPipelines.value).toEqual([{ id: 'pl_review', name: 'review' }])
155
+ expect(hasIssues.value).toBe(true)
156
+ // A brand-new built-in is not "invalid" or "outdated" — those only concern STORED pipelines.
157
+ expect(invalid.value).toHaveLength(0)
158
+ expect(outdated.value).toHaveLength(0)
159
+ })
160
+
161
+ it('reports no new pipelines when every catalog id is already stored', () => {
162
+ const stored = builtin(['coder', 'reviewer'], { id: 'pl_full', version: 1 })
163
+ const { newPipelines, hasIssues } = scan([stored], { pl_full: 1 })
164
+ expect(newPipelines.value).toHaveLength(0)
165
+ expect(hasIssues.value).toBe(false)
166
+ })
145
167
  })
@@ -23,6 +23,23 @@ export interface PipelineHealth {
23
23
  outdated: boolean
24
24
  }
25
25
 
26
+ /** A brand-new built-in pipeline that appeared in the catalog but isn't in the workspace yet. */
27
+ export interface NewPipeline {
28
+ /** The catalog (built-in) id — what the reseed endpoint is keyed by (it creates the row). */
29
+ id: string
30
+ /** The built-in's display name, from the catalog versions' companion name map. */
31
+ name: string
32
+ }
33
+
34
+ /**
35
+ * A built-in's display name for the "new pipeline" advisory, humanised from its catalog id
36
+ * (`pl_review` -> "review", rendered capitalised) — used only until the row is reseeded into
37
+ * existence, at which point its real catalog name is stored. Mirrors `useRiskPolicyHealth`.
38
+ */
39
+ function builtinPipelineName(id: string): string {
40
+ return id.replace(/^pl_/, '').replace(/_/g, ' ')
41
+ }
42
+
26
43
  /** Producers a companion kind is allowed to review (inverse of {@link COMPANION_FOR_PRODUCER}). */
27
44
  function companionTargets(companion: string): string[] {
28
45
  return Object.entries(COMPANION_FOR_PRODUCER)
@@ -88,12 +105,14 @@ function shapeProblem(p: Pipeline): string | null {
88
105
 
89
106
  /**
90
107
  * Detect pipelines in an unhealthy state for the startup advisory: those referencing an unknown
91
- * agent kind or with an invalid shape (offer to delete a custom one / reseed a built-in), and
92
- * built-ins whose seeded definition has moved ahead of the stored copy (offer to reseed). Reads
93
- * the pipeline library + the snapshot's catalog versions from the pipelines store. Detection runs
94
- * entirely client-side because the canonical agent-kind catalog lives here (`AGENT_BY_KIND` +
95
- * `SYSTEM_AGENT_META` + registered custom kinds); the version comparison uses the catalog
96
- * versions the snapshot ships.
108
+ * agent kind or with an invalid shape (offer to delete a custom one / reseed a built-in), built-ins
109
+ * whose seeded definition has moved ahead of the stored copy (offer to reseed), AND brand-new
110
+ * built-ins that appeared in the catalog but aren't in the workspace yet (offer to ADD them — a
111
+ * board seeded before the built-in shipped, e.g. `pl_review`). Reads the pipeline library + the
112
+ * snapshot's catalog versions from the pipelines store. Detection runs entirely client-side: the
113
+ * canonical agent-kind catalog lives here (`AGENT_BY_KIND` + `SYSTEM_AGENT_META` + registered custom
114
+ * kinds), and the catalog versions the snapshot ships ARE the set of built-in ids — a catalog id
115
+ * with no stored pipeline is a new built-in. Mirrors `useRiskPolicyHealth` / `useModelPresetHealth`.
97
116
  */
98
117
  export function usePipelineHealth() {
99
118
  const store = usePipelinesStore()
@@ -130,11 +149,20 @@ export function usePipelineHealth() {
130
149
  return out
131
150
  })
132
151
 
152
+ // Brand-new built-ins: a catalog id (a `catalogVersions` key) with no stored pipeline. Adding one
153
+ // is the same reseed call as adopting an update (it inserts the row when absent).
154
+ const newPipelines = computed<NewPipeline[]>(() => {
155
+ const storedIds = new Set(store.pipelines.map((p) => p.id))
156
+ return Object.keys(store.catalogVersions)
157
+ .filter((id) => !storedIds.has(id))
158
+ .map((id) => ({ id, name: builtinPipelineName(id) }))
159
+ })
160
+
133
161
  // An invalid built-in is reseeded (not deleted) and that also clears any "outdated" flag, so
134
162
  // exclude it from the outdated list to avoid offering the same fix twice.
135
163
  const invalid = computed(() => health.value.filter((h) => h.invalid))
136
164
  const outdated = computed(() => health.value.filter((h) => h.outdated && !h.invalid))
137
- const hasIssues = computed(() => health.value.length > 0)
165
+ const hasIssues = computed(() => health.value.length > 0 || newPipelines.value.length > 0)
138
166
 
139
- return { health, invalid, outdated, hasIssues }
167
+ return { health, invalid, outdated, newPipelines, hasIssues }
140
168
  }
@@ -227,6 +227,7 @@ export const useBoardStore = defineStore('board', () => {
227
227
  modelPresetId?: string
228
228
  pipelineId?: string
229
229
  agentConfig?: Record<string, string>
230
+ fragmentIds?: string[]
230
231
  technical?: boolean
231
232
  },
232
233
  ): Promise<Block | undefined> {
@@ -240,6 +241,7 @@ export const useBoardStore = defineStore('board', () => {
240
241
  ...(options?.modelPresetId ? { modelPresetId: options.modelPresetId } : {}),
241
242
  ...(options?.pipelineId ? { pipelineId: options.pipelineId } : {}),
242
243
  ...(options?.agentConfig ? { agentConfig: options.agentConfig } : {}),
244
+ ...(options?.fragmentIds?.length ? { fragmentIds: options.fragmentIds } : {}),
243
245
  ...(options?.technical ? { technical: true } : {}),
244
246
  })
245
247
  upsert(block)
@@ -17,10 +17,19 @@ describe('pipelineAllowedForTaskType', () => {
17
17
  expect(pipelineAllowedForTaskType(pipeline({ purpose: undefined }), 'document')).toBe(false)
18
18
  })
19
19
 
20
- it('every non-document task type is unrestricted (any purpose, and undefined type)', () => {
21
- for (const type of ['feature', 'bug', 'spike', 'review', 'ralph', undefined] as const) {
20
+ it('a review task offers ONLY review-purpose pipelines', () => {
21
+ expect(pipelineAllowedForTaskType(pipeline({ purpose: 'review' }), 'review')).toBe(true)
22
+ expect(pipelineAllowedForTaskType(pipeline({ purpose: 'build' }), 'review')).toBe(false)
23
+ expect(pipelineAllowedForTaskType(pipeline({ purpose: 'document' }), 'review')).toBe(false)
24
+ // An unclassified pipeline is hidden from a review task (it requires the explicit classifier).
25
+ expect(pipelineAllowedForTaskType(pipeline({ purpose: undefined }), 'review')).toBe(false)
26
+ })
27
+
28
+ it('every other task type is unrestricted (any purpose, and undefined type)', () => {
29
+ for (const type of ['feature', 'bug', 'spike', 'ralph', undefined] as const) {
22
30
  expect(pipelineAllowedForTaskType(pipeline({ purpose: 'build' }), type)).toBe(true)
23
31
  expect(pipelineAllowedForTaskType(pipeline({ purpose: 'document' }), type)).toBe(true)
32
+ expect(pipelineAllowedForTaskType(pipeline({ purpose: 'review' }), type)).toBe(true)
24
33
  expect(pipelineAllowedForTaskType(pipeline({ purpose: undefined }), type)).toBe(true)
25
34
  }
26
35
  })
@@ -51,10 +60,15 @@ describe('pipelineAllowedForManualStart composes the task-type gate', () => {
51
60
  const noFrame = undefined
52
61
  const blocks: Block[] = []
53
62
 
54
- it('drops a non-document pipeline for a document task, keeps it for others', () => {
63
+ it('drops a mismatched pipeline for a document / review task, keeps it for others', () => {
55
64
  const build = pipeline({ purpose: 'build' })
56
65
  expect(pipelineAllowedForManualStart(build, noFrame, blocks, 'document')).toBe(false)
66
+ expect(pipelineAllowedForManualStart(build, noFrame, blocks, 'review')).toBe(false)
57
67
  expect(pipelineAllowedForManualStart(build, noFrame, blocks, 'feature')).toBe(true)
68
+ // A review pipeline is offered to a review task and hidden from a document task.
69
+ const review = pipeline({ purpose: 'review' })
70
+ expect(pipelineAllowedForManualStart(review, noFrame, blocks, 'review')).toBe(true)
71
+ expect(pipelineAllowedForManualStart(review, noFrame, blocks, 'document')).toBe(false)
58
72
  // No task type supplied ⇒ no task-type restriction.
59
73
  expect(pipelineAllowedForManualStart(build, noFrame, blocks)).toBe(true)
60
74
  })
@@ -2084,6 +2084,7 @@
2084
2084
  "recurringNoFrame": "Eine wiederkehrende Aufgabe muss auf einem Service liegen. Fügen Sie sie aus einem Service-Frame (oder einem darin enthaltenen Modul) hinzu.",
2085
2085
  "titleField": "Titel",
2086
2086
  "titlePlaceholder": "Was muss getan werden?",
2087
+ "titlePlaceholderReview": "Optional – wird aus dem Pull Request abgeleitet",
2087
2088
  "issueIncluded": "{title} (aus Issue, enthalten)",
2088
2089
  "loadingIssue": "Beschreibung des verknüpften Issues wird geladen…",
2089
2090
  "additionalNotes": "Zusätzliche Notizen",
@@ -2172,6 +2173,8 @@
2172
2173
  "defaultPreset": "Standard ({name}) — {thresholds}",
2173
2174
  "defaultModelPreset": "Standard ({name})",
2174
2175
  "modelPreset": "Modell-Preset",
2176
+ "bestPractices": "Best Practices",
2177
+ "bestPracticesHint": "Best-Practice-Fragmente anheften, damit die Agenten dieser Aufgabe sie zusätzlich zu den Standards auf Service-Ebene befolgen.",
2175
2178
  "agentConfiguration": "Agent-Konfiguration",
2176
2179
  "contextDocuments": "Kontextdokumente",
2177
2180
  "contextIssues": "Kontext-Issues",
@@ -2195,7 +2198,9 @@
2195
2198
  "prUrl": "Pull Request",
2196
2199
  "prUrlHint": "URL oder Nummer des zu prüfenden Pull Requests",
2197
2200
  "focus": "Prüfungsschwerpunkt",
2198
- "focusPlaceholder": "z. B. Fokus auf die Auth-Änderungen und Fehlerbehandlung"
2201
+ "focusPlaceholder": "z. B. Fokus auf die Auth-Änderungen und Fehlerbehandlung",
2202
+ "derivedTitle": "{ref} prüfen",
2203
+ "derivedTitleFallback": "Pull Request prüfen"
2199
2204
  }
2200
2205
  },
2201
2206
  "recurring": {
@@ -3260,6 +3265,9 @@
3260
3265
  "invalidHeading": "Ungültige Pipelines",
3261
3266
  "invalidDescription": "Diese verweisen auf einen fehlenden Agenten oder sind falsch konfiguriert, sodass sie beim Start fehlschlagen (oder falsch laufen) würden. Löschen Sie eine benutzerdefinierte Pipeline oder setzen Sie eine integrierte neu auf, um ihre Katalogdefinition wiederherzustellen.",
3262
3267
  "builtinBadge": "integriert",
3268
+ "newHeading": "Neue Pipelines verfügbar",
3269
+ "newDescription": "Neue integrierte Pipelines sind verfügbar. Füge sie zur Bibliothek dieses Boards hinzu.",
3270
+ "add": "Hinzufügen",
3263
3271
  "reseed": "Neu aufsetzen",
3264
3272
  "delete": "Löschen",
3265
3273
  "updatesHeading": "Updates verfügbar",
@@ -203,6 +203,7 @@
203
203
  "recurringNoFrame": "A recurring task must live on a service. Add it from a service frame (or a module inside one).",
204
204
  "titleField": "Title",
205
205
  "titlePlaceholder": "What needs to be done?",
206
+ "titlePlaceholderReview": "Optional — derived from the pull request",
206
207
  "issueIncluded": "{title} (from issue, included)",
207
208
  "loadingIssue": "Loading the linked issue's description…",
208
209
  "additionalNotes": "Additional notes",
@@ -291,6 +292,8 @@
291
292
  "defaultPreset": "Default ({name}) — {thresholds}",
292
293
  "defaultModelPreset": "Default ({name})",
293
294
  "modelPreset": "Model preset",
295
+ "bestPractices": "Best practices",
296
+ "bestPracticesHint": "Pin best-practice fragments so this task's agents follow them, on top of the service-level standards.",
294
297
  "agentConfiguration": "Agent configuration",
295
298
  "contextDocuments": "Context documents",
296
299
  "contextIssues": "Context issues",
@@ -317,7 +320,9 @@
317
320
  "prUrl": "Pull request",
318
321
  "prUrlHint": "URL or number of the pull request to review",
319
322
  "focus": "Review focus",
320
- "focusPlaceholder": "e.g. focus on the auth changes and error handling"
323
+ "focusPlaceholder": "e.g. focus on the auth changes and error handling",
324
+ "derivedTitle": "Review {ref}",
325
+ "derivedTitleFallback": "Review pull request"
321
326
  }
322
327
  },
323
328
  "recurring": {
@@ -3626,6 +3631,9 @@
3626
3631
  "invalidHeading": "Invalid pipelines",
3627
3632
  "invalidDescription": "These reference a missing agent or are misconfigured, so they would fail (or misrun) at start. Delete a custom pipeline, or reseed a built-in to restore its catalog definition.",
3628
3633
  "builtinBadge": "built-in",
3634
+ "newHeading": "New pipelines available",
3635
+ "newDescription": "New built-in pipelines have shipped. Add them to this board's library.",
3636
+ "add": "Add",
3629
3637
  "reseed": "Reseed",
3630
3638
  "delete": "Delete",
3631
3639
  "updatesHeading": "Updates available",
@@ -182,6 +182,7 @@
182
182
  "recurringNoFrame": "Una tarea recurrente debe vivir en un servicio. Añádela desde un marco de servicio (o un módulo dentro de él).",
183
183
  "titleField": "Título",
184
184
  "titlePlaceholder": "¿Qué hay que hacer?",
185
+ "titlePlaceholderReview": "Opcional: se deriva de la pull request",
185
186
  "issueIncluded": "{title} (de la incidencia, incluida)",
186
187
  "loadingIssue": "Cargando la descripción de la incidencia vinculada…",
187
188
  "additionalNotes": "Notas adicionales",
@@ -270,6 +271,8 @@
270
271
  "defaultPreset": "Predeterminado ({name}): {thresholds}",
271
272
  "defaultModelPreset": "Predeterminado ({name})",
272
273
  "modelPreset": "Preset de modelo",
274
+ "bestPractices": "Buenas prácticas",
275
+ "bestPracticesHint": "Fija fragmentos de buenas prácticas para que los agentes de esta tarea los sigan, además de los estándares del servicio.",
273
276
  "agentConfiguration": "Configuración del agente",
274
277
  "contextDocuments": "Documentos de contexto",
275
278
  "contextIssues": "Incidencias de contexto",
@@ -293,7 +296,9 @@
293
296
  "prUrl": "Pull request",
294
297
  "prUrlHint": "URL o número de la pull request a revisar",
295
298
  "focus": "Enfoque de la revisión",
296
- "focusPlaceholder": "p. ej., céntrate en los cambios de autenticación y el manejo de errores"
299
+ "focusPlaceholder": "p. ej., céntrate en los cambios de autenticación y el manejo de errores",
300
+ "derivedTitle": "Revisar {ref}",
301
+ "derivedTitleFallback": "Revisar la pull request"
297
302
  }
298
303
  },
299
304
  "recurring": {
@@ -3527,6 +3532,9 @@
3527
3532
  "invalidHeading": "Pipelines no válidos",
3528
3533
  "invalidDescription": "Estos hacen referencia a un agente inexistente o están mal configurados, por lo que fallarían (o se ejecutarían mal) al iniciar. Elimina un pipeline personalizado o vuelve a generar uno integrado para restaurar su definición del catálogo.",
3529
3534
  "builtinBadge": "integrado",
3535
+ "newHeading": "Nuevos pipelines disponibles",
3536
+ "newDescription": "Hay nuevos pipelines integrados. Añádelos a la biblioteca de este tablero.",
3537
+ "add": "Añadir",
3530
3538
  "reseed": "Regenerar",
3531
3539
  "delete": "Eliminar",
3532
3540
  "updatesHeading": "Actualizaciones disponibles",
@@ -182,6 +182,7 @@
182
182
  "recurringNoFrame": "Une tâche récurrente doit appartenir à un service. Ajoutez-la depuis un cadre de service (ou un module à l’intérieur).",
183
183
  "titleField": "Titre",
184
184
  "titlePlaceholder": "Que faut-il faire ?",
185
+ "titlePlaceholderReview": "Facultatif — dérivé de la pull request",
185
186
  "issueIncluded": "{title} (depuis le ticket, incluse)",
186
187
  "loadingIssue": "Chargement de la description du ticket lié…",
187
188
  "additionalNotes": "Notes supplémentaires",
@@ -270,6 +271,8 @@
270
271
  "defaultPreset": "Par défaut ({name}) : {thresholds}",
271
272
  "defaultModelPreset": "Par défaut ({name})",
272
273
  "modelPreset": "Préréglage de modèle",
274
+ "bestPractices": "Bonnes pratiques",
275
+ "bestPracticesHint": "Épinglez des fragments de bonnes pratiques pour que les agents de cette tâche les suivent, en plus des standards au niveau du service.",
273
276
  "agentConfiguration": "Configuration de l’agent",
274
277
  "contextDocuments": "Documents de contexte",
275
278
  "contextIssues": "Tickets de contexte",
@@ -293,7 +296,9 @@
293
296
  "prUrl": "Pull request",
294
297
  "prUrlHint": "URL ou numéro de la pull request à examiner",
295
298
  "focus": "Objet de la revue",
296
- "focusPlaceholder": "p. ex. concentrez-vous sur les changements d'authentification et la gestion des erreurs"
299
+ "focusPlaceholder": "p. ex. concentrez-vous sur les changements d'authentification et la gestion des erreurs",
300
+ "derivedTitle": "Examiner {ref}",
301
+ "derivedTitleFallback": "Examiner la pull request"
297
302
  }
298
303
  },
299
304
  "recurring": {
@@ -3527,6 +3532,9 @@
3527
3532
  "invalidHeading": "Pipelines non valides",
3528
3533
  "invalidDescription": "Ceux-ci référencent un agent manquant ou sont mal configurés et échoueraient (ou s'exécuteraient mal) au démarrage. Supprimez un pipeline personnalisé, ou régénérez un pipeline intégré pour restaurer sa définition du catalogue.",
3529
3534
  "builtinBadge": "intégré",
3535
+ "newHeading": "Nouveaux pipelines disponibles",
3536
+ "newDescription": "De nouveaux pipelines intégrés sont arrivés. Ajoutez-les à la bibliothèque de ce tableau.",
3537
+ "add": "Ajouter",
3530
3538
  "reseed": "Régénérer",
3531
3539
  "delete": "Supprimer",
3532
3540
  "updatesHeading": "Mises à jour disponibles",
@@ -182,6 +182,7 @@
182
182
  "recurringNoFrame": "משימה מחזורית חייבת להתקיים על שירות. הוסף אותה ממסגרת שירות (או ממודול בתוכה).",
183
183
  "titleField": "כותרת",
184
184
  "titlePlaceholder": "מה צריך לעשות?",
185
+ "titlePlaceholderReview": "אופציונלי — נגזר מבקשת המשיכה",
185
186
  "issueIncluded": "{title} (מהאישיו, כלול)",
186
187
  "loadingIssue": "טוען את תיאור האישיו המקושר…",
187
188
  "additionalNotes": "הערות נוספות",
@@ -270,6 +271,8 @@
270
271
  "defaultPreset": "ברירת מחדל ({name}) — {thresholds}",
271
272
  "defaultModelPreset": "ברירת מחדל ({name})",
272
273
  "modelPreset": "הגדרה קבועה של מודל",
274
+ "bestPractices": "מומלצות עבודה",
275
+ "bestPracticesHint": "הצמידו מקטעי מומלצות עבודה כדי שסוכני המשימה יפעלו לפיהם, בנוסף לסטנדרטים ברמת השירות.",
273
276
  "agentConfiguration": "תצורת סוכן",
274
277
  "contextDocuments": "מסמכי הקשר",
275
278
  "contextIssues": "אישיו הקשר",
@@ -293,7 +296,9 @@
293
296
  "prUrl": "בקשת משיכה",
294
297
  "prUrlHint": "כתובת או מספר של בקשת המשיכה לסקירה",
295
298
  "focus": "מוקד הסקירה",
296
- "focusPlaceholder": "לדוגמה, להתמקד בשינויי האימות ובטיפול בשגיאות"
299
+ "focusPlaceholder": "לדוגמה, להתמקד בשינויי האימות ובטיפול בשגיאות",
300
+ "derivedTitle": "סקירת {ref}",
301
+ "derivedTitleFallback": "סקירת בקשת המשיכה"
297
302
  }
298
303
  },
299
304
  "recurring": {
@@ -3538,6 +3543,9 @@
3538
3543
  "invalidHeading": "צינורות לא תקפים",
3539
3544
  "invalidDescription": "אלה מפנים לסוכן חסר או מוגדרים בצורה שגויה, ולכן ייכשלו (או ירוצו בצורה שגויה) בהתחלה. מחק צינור מותאם אישית, או זרע מחדש צינור מובנה כדי לשחזר את הגדרת הקטלוג שלו.",
3540
3545
  "builtinBadge": "מובנה",
3546
+ "newHeading": "צינורות חדשים זמינים",
3547
+ "newDescription": "הגיעו צינורות מובנים חדשים. הוסף אותם לספריית הלוח הזה.",
3548
+ "add": "הוסף",
3541
3549
  "reseed": "זרע מחדש",
3542
3550
  "delete": "מחק",
3543
3551
  "updatesHeading": "עדכונים זמינים",
@@ -2084,6 +2084,7 @@
2084
2084
  "recurringNoFrame": "Un'attività ricorrente deve risiedere su un servizio. Aggiungila da un frame di servizio (o da un modulo al suo interno).",
2085
2085
  "titleField": "Titolo",
2086
2086
  "titlePlaceholder": "Cosa bisogna fare?",
2087
+ "titlePlaceholderReview": "Facoltativo — derivato dalla pull request",
2087
2088
  "issueIncluded": "{title} (dall'issue, incluso)",
2088
2089
  "loadingIssue": "Caricamento della descrizione dell'issue collegata…",
2089
2090
  "additionalNotes": "Note aggiuntive",
@@ -2172,6 +2173,8 @@
2172
2173
  "defaultPreset": "Predefinito ({name}) — {thresholds}",
2173
2174
  "defaultModelPreset": "Predefinito ({name})",
2174
2175
  "modelPreset": "Preset di modello",
2176
+ "bestPractices": "Best practice",
2177
+ "bestPracticesHint": "Fissa i frammenti di best practice affinché gli agenti di questa attività li seguano, oltre agli standard a livello di servizio.",
2175
2178
  "agentConfiguration": "Configurazione dell'agente",
2176
2179
  "contextDocuments": "Documenti di contesto",
2177
2180
  "contextIssues": "Issue di contesto",
@@ -2195,7 +2198,9 @@
2195
2198
  "prUrl": "Pull request",
2196
2199
  "prUrlHint": "URL o numero della pull request da revisionare",
2197
2200
  "focus": "Focus della revisione",
2198
- "focusPlaceholder": "es. concentrati sulle modifiche di autenticazione e sulla gestione degli errori"
2201
+ "focusPlaceholder": "es. concentrati sulle modifiche di autenticazione e sulla gestione degli errori",
2202
+ "derivedTitle": "Rivedi {ref}",
2203
+ "derivedTitleFallback": "Rivedi la pull request"
2199
2204
  }
2200
2205
  },
2201
2206
  "recurring": {
@@ -3260,6 +3265,9 @@
3260
3265
  "invalidHeading": "Pipeline non valide",
3261
3266
  "invalidDescription": "Queste fanno riferimento a un agente mancante o sono configurate male, quindi fallirebbero (o funzionerebbero male) all'avvio. Elimina una pipeline personalizzata, oppure ripristina una integrata per recuperare la sua definizione dal catalogo.",
3262
3267
  "builtinBadge": "integrata",
3268
+ "newHeading": "Nuove pipeline disponibili",
3269
+ "newDescription": "Sono state rilasciate nuove pipeline integrate. Aggiungile alla libreria di questa board.",
3270
+ "add": "Aggiungi",
3263
3271
  "reseed": "Ripristina",
3264
3272
  "delete": "Elimina",
3265
3273
  "updatesHeading": "Aggiornamenti disponibili",
@@ -182,6 +182,7 @@
182
182
  "recurringNoFrame": "繰り返しタスクはサービス上に配置する必要があります。サービスフレーム(またはその中のモジュール)から追加してください。",
183
183
  "titleField": "タイトル",
184
184
  "titlePlaceholder": "何を実施する必要がありますか?",
185
+ "titlePlaceholderReview": "任意 — プルリクエストから生成されます",
185
186
  "issueIncluded": "{title}(issue から、含む)",
186
187
  "loadingIssue": "リンクされた issue の説明を読み込み中…",
187
188
  "additionalNotes": "追加メモ",
@@ -270,6 +271,8 @@
270
271
  "defaultPreset": "既定({name})— {thresholds}",
271
272
  "defaultModelPreset": "既定({name})",
272
273
  "modelPreset": "モデルプリセット",
274
+ "bestPractices": "ベストプラクティス",
275
+ "bestPracticesHint": "サービスレベルの標準に加えて、このタスクのエージェントが従うようにベストプラクティスのフラグメントを固定します。",
273
276
  "agentConfiguration": "エージェント設定",
274
277
  "contextDocuments": "コンテキストドキュメント",
275
278
  "contextIssues": "コンテキスト issue",
@@ -293,7 +296,9 @@
293
296
  "prUrl": "プルリクエスト",
294
297
  "prUrlHint": "レビュー対象のプルリクエストのURLまたは番号",
295
298
  "focus": "レビューの重点",
296
- "focusPlaceholder": "例: 認証の変更とエラー処理に注目"
299
+ "focusPlaceholder": "例: 認証の変更とエラー処理に注目",
300
+ "derivedTitle": "{ref} をレビュー",
301
+ "derivedTitleFallback": "プルリクエストをレビュー"
297
302
  }
298
303
  },
299
304
  "recurring": {
@@ -3539,6 +3544,9 @@
3539
3544
  "invalidHeading": "無効なパイプライン",
3540
3545
  "invalidDescription": "存在しないエージェントを参照しているか設定が誤っているため、開始時に失敗(または誤動作)します。カスタムパイプラインを削除するか、ビルトインを再シードしてカタログ定義を復元してください。",
3541
3546
  "builtinBadge": "ビルトイン",
3547
+ "newHeading": "新しいパイプラインがあります",
3548
+ "newDescription": "新しい組み込みパイプラインが追加されました。このボードのライブラリに追加してください。",
3549
+ "add": "追加",
3542
3550
  "reseed": "再シード",
3543
3551
  "delete": "削除",
3544
3552
  "updatesHeading": "更新あり",
@@ -182,6 +182,7 @@
182
182
  "recurringNoFrame": "Zadanie cykliczne musi należeć do usługi. Dodaj je z ramki usługi (lub modułu w jej obrębie).",
183
183
  "titleField": "Tytuł",
184
184
  "titlePlaceholder": "Co trzeba zrobić?",
185
+ "titlePlaceholderReview": "Opcjonalnie — tworzony na podstawie pull requesta",
185
186
  "issueIncluded": "{title} (ze zgłoszenia, dołączone)",
186
187
  "loadingIssue": "Wczytywanie opisu powiązanego zgłoszenia…",
187
188
  "additionalNotes": "Dodatkowe uwagi",
@@ -270,6 +271,8 @@
270
271
  "defaultPreset": "Domyślny ({name}): {thresholds}",
271
272
  "defaultModelPreset": "Domyślny ({name})",
272
273
  "modelPreset": "Preset modelu",
274
+ "bestPractices": "Dobre praktyki",
275
+ "bestPracticesHint": "Przypnij fragmenty dobrych praktyk, aby agenci tego zadania stosowali je oprócz standardów na poziomie usługi.",
273
276
  "agentConfiguration": "Konfiguracja agenta",
274
277
  "contextDocuments": "Dokumenty kontekstu",
275
278
  "contextIssues": "Zgłoszenia kontekstu",
@@ -293,7 +296,9 @@
293
296
  "prUrl": "Pull request",
294
297
  "prUrlHint": "URL lub numer pull requesta do przeglądu",
295
298
  "focus": "Zakres przeglądu",
296
- "focusPlaceholder": "np. skup się na zmianach uwierzytelniania i obsłudze błędów"
299
+ "focusPlaceholder": "np. skup się na zmianach uwierzytelniania i obsłudze błędów",
300
+ "derivedTitle": "Przegląd {ref}",
301
+ "derivedTitleFallback": "Przegląd pull requesta"
297
302
  }
298
303
  },
299
304
  "recurring": {
@@ -3527,6 +3532,9 @@
3527
3532
  "invalidHeading": "Nieprawidłowe pipeline'y",
3528
3533
  "invalidDescription": "Odwołują się do brakującego agenta lub są źle skonfigurowane, więc zawiodłyby (lub wykonałyby się błędnie) przy starcie. Usuń pipeline niestandardowy albo zregeneruj wbudowany, aby przywrócić jego definicję z katalogu.",
3529
3534
  "builtinBadge": "wbudowany",
3535
+ "newHeading": "Dostępne nowe pipeline'y",
3536
+ "newDescription": "Pojawiły się nowe wbudowane pipeline'y. Dodaj je do biblioteki tej tablicy.",
3537
+ "add": "Dodaj",
3530
3538
  "reseed": "Zregeneruj",
3531
3539
  "delete": "Usuń",
3532
3540
  "updatesHeading": "Dostępne aktualizacje",
@@ -182,6 +182,7 @@
182
182
  "recurringNoFrame": "Yinelenen bir görev bir serviste bulunmalıdır. Bir servis çerçevesinden (veya içindeki bir modülden) ekleyin.",
183
183
  "titleField": "Başlık",
184
184
  "titlePlaceholder": "Ne yapılması gerekiyor?",
185
+ "titlePlaceholderReview": "İsteğe bağlı — pull request'ten türetilir",
185
186
  "issueIncluded": "{title} (sorundan, dahil edildi)",
186
187
  "loadingIssue": "Bağlantılı sorunun açıklaması yükleniyor…",
187
188
  "additionalNotes": "Ek notlar",
@@ -270,6 +271,8 @@
270
271
  "defaultPreset": "Varsayılan ({name}) — {thresholds}",
271
272
  "defaultModelPreset": "Varsayılan ({name})",
272
273
  "modelPreset": "Model ön ayarı",
274
+ "bestPractices": "En iyi uygulamalar",
275
+ "bestPracticesHint": "Bu görevin ajanlarının hizmet düzeyindeki standartlara ek olarak uygulaması için en iyi uygulama parçacıklarını sabitleyin.",
273
276
  "agentConfiguration": "Ajan yapılandırması",
274
277
  "contextDocuments": "Bağlam belgeleri",
275
278
  "contextIssues": "Bağlam sorunları",
@@ -293,7 +296,9 @@
293
296
  "prUrl": "Pull request",
294
297
  "prUrlHint": "İncelenecek pull request'in URL'si veya numarası",
295
298
  "focus": "İnceleme odağı",
296
- "focusPlaceholder": "örn. kimlik doğrulama değişikliklerine ve hata yönetimine odaklan"
299
+ "focusPlaceholder": "örn. kimlik doğrulama değişikliklerine ve hata yönetimine odaklan",
300
+ "derivedTitle": "{ref} incele",
301
+ "derivedTitleFallback": "Pull request'i incele"
297
302
  }
298
303
  },
299
304
  "recurring": {
@@ -3539,6 +3544,9 @@
3539
3544
  "invalidHeading": "Geçersiz pipeline'lar",
3540
3545
  "invalidDescription": "Bunlar eksik bir agent'a referans veriyor veya yanlış yapılandırılmış, bu yüzden başlangıçta başarısız olur (veya hatalı çalışır). Özel bir pipeline'ı silin ya da katalog tanımını geri yüklemek için bir yerleşik pipeline'ı yeniden tohumlayın.",
3541
3546
  "builtinBadge": "yerleşik",
3547
+ "newHeading": "Yeni pipeline'lar mevcut",
3548
+ "newDescription": "Yeni yerleşik pipeline'lar geldi. Bu panonun kütüphanesine ekleyin.",
3549
+ "add": "Ekle",
3542
3550
  "reseed": "Yeniden tohumla",
3543
3551
  "delete": "Sil",
3544
3552
  "updatesHeading": "Güncellemeler mevcut",
@@ -182,6 +182,7 @@
182
182
  "recurringNoFrame": "Періодичне завдання має належати сервісу. Додайте його з рамки сервісу (або модуля всередині неї).",
183
183
  "titleField": "Назва",
184
184
  "titlePlaceholder": "Що потрібно зробити?",
185
+ "titlePlaceholderReview": "Необов’язково — формується з pull request",
185
186
  "issueIncluded": "{title} (зі звернення, включено)",
186
187
  "loadingIssue": "Завантаження опису пов’язаного звернення…",
187
188
  "additionalNotes": "Додаткові примітки",
@@ -270,6 +271,8 @@
270
271
  "defaultPreset": "Типовий ({name}): {thresholds}",
271
272
  "defaultModelPreset": "Типовий ({name})",
272
273
  "modelPreset": "Пресет моделі",
274
+ "bestPractices": "Найкращі практики",
275
+ "bestPracticesHint": "Закріпіть фрагменти найкращих практик, щоб агенти цього завдання дотримувалися їх додатково до стандартів рівня сервісу.",
273
276
  "agentConfiguration": "Налаштування агента",
274
277
  "contextDocuments": "Документи контексту",
275
278
  "contextIssues": "Звернення контексту",
@@ -293,7 +296,9 @@
293
296
  "prUrl": "Pull request",
294
297
  "prUrlHint": "URL або номер pull request для огляду",
295
298
  "focus": "Фокус огляду",
296
- "focusPlaceholder": "напр. зосередься на змінах автентифікації та обробці помилок"
299
+ "focusPlaceholder": "напр. зосередься на змінах автентифікації та обробці помилок",
300
+ "derivedTitle": "Огляд {ref}",
301
+ "derivedTitleFallback": "Огляд pull request"
297
302
  }
298
303
  },
299
304
  "recurring": {
@@ -3527,6 +3532,9 @@
3527
3532
  "invalidHeading": "Недійсні пайплайни",
3528
3533
  "invalidDescription": "Вони посилаються на відсутнього агента або неправильно налаштовані, тож зазнали б помилки (або виконалися б неправильно) під час старту. Видаліть власний пайплайн або перегенеруйте вбудований, щоб відновити його визначення з каталогу.",
3529
3534
  "builtinBadge": "вбудований",
3535
+ "newHeading": "Доступні нові пайплайни",
3536
+ "newDescription": "З'явилися нові вбудовані пайплайни. Додайте їх до бібліотеки цієї дошки.",
3537
+ "add": "Додати",
3530
3538
  "reseed": "Перегенерувати",
3531
3539
  "delete": "Видалити",
3532
3540
  "updatesHeading": "Доступні оновлення",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cat-factory/app",
3
- "version": "0.141.2",
3
+ "version": "0.143.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.40",
42
42
  "wretch": "^3.0.9",
43
- "@cat-factory/contracts": "0.150.0"
43
+ "@cat-factory/contracts": "0.152.0"
44
44
  },
45
45
  "devDependencies": {
46
46
  "@toad-contracts/testing": "0.3.2",