@cat-factory/app 0.140.0 → 0.141.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.
@@ -288,17 +288,34 @@ const selectedModelPresetLabel = computed(() => {
288
288
 
289
289
  // Hide UI-testing pipelines (`tester-ui` / `visual-confirmation`) when the target frame has no
290
290
  // UI to exercise — they'd be refused server-side (see utils/pipeline + the backend gate). Also
291
- // hide `'recurring'`-only pipelines: a one-off task start of one is refused at run start.
291
+ // 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.
292
294
  const selectablePipelines = computed(() =>
293
- pipelines.pipelines.filter((p) => pipelineAllowedForManualStart(p, frame.value, board.blocks)),
295
+ pipelines.pipelines.filter((p) =>
296
+ pipelineAllowedForManualStart(p, frame.value, board.blocks, taskType.value),
297
+ ),
294
298
  )
295
- // Picking the Ralph loop task type auto-selects its pipeline, so the per-task validation
296
- // command + iteration budget (contributed by the `ralph` agent) surface immediately — the
297
- // loop is meaningless without them, so "choose at run time" would be a dead end here.
299
+ // Some task types want their type-default pipeline surfaced in the modal up front, so picking the
300
+ // type auto-selects it (the user can still change it among the still-offered pipelines). This is a
301
+ // DELIBERATE SUBSET of the backend `defaultPipelineIdForTaskType` only the types whose default
302
+ // must appear in the form BEFORE creation:
303
+ // - `ralph` needs its preset so the per-task validation command + iteration budget the `ralph`
304
+ // 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.
310
+ const DEFAULT_PIPELINE_FOR_TYPE: Partial<Record<TaskTypeChoice, string>> = {
311
+ ralph: 'pl_ralph',
312
+ document: 'pl_document',
313
+ }
298
314
  watch(taskType, (next) => {
299
- if (next !== 'ralph') return
300
- const ralph = pipelines.pipelines.find((p) => p.id === 'pl_ralph')
301
- if (ralph) pipelineId.value = ralph.id
315
+ const preset = DEFAULT_PIPELINE_FOR_TYPE[next]
316
+ if (!preset) return
317
+ const match = pipelines.pipelines.find((p) => p.id === preset)
318
+ if (match) pipelineId.value = match.id
302
319
  })
303
320
 
304
321
  // Task-level agent config contributed by the selected pipeline's agents (e.g. the
@@ -455,7 +472,10 @@ watch(open, (isOpen) => {
455
472
  delete docKindFieldValues[key]
456
473
  riskPolicyId.value = ''
457
474
  modelPresetId.value = ''
458
- pipelineId.value = ''
475
+ // Seed the pipeline from the (possibly doc-repo-forced) task type's default, so a document
476
+ // repo opens with `pl_document` pre-selected rather than empty. This runs AFTER the `taskType`
477
+ // watcher fired during this reset, so it is the authoritative default (see DEFAULT_PIPELINE_FOR_TYPE).
478
+ pipelineId.value = DEFAULT_PIPELINE_FOR_TYPE[taskType.value] ?? ''
459
479
  agentConfigValues.value = {}
460
480
  pendingContext.value = []
461
481
  showDocPicker.value = false
@@ -28,12 +28,13 @@ const deps = computed(() =>
28
28
  (block.value?.dependsOn ?? []).map((id) => board.getBlock(id)).filter((b): b is Block => !!b),
29
29
  )
30
30
 
31
- // Hide UI-testing pipelines when this block's frame has no UI to exercise, and `'recurring'`-only
32
- // pipelines (a manual run of one is refused server-side) — see the backend gate.
31
+ // Hide UI-testing pipelines when this block's frame has no UI to exercise, `'recurring'`-only
32
+ // pipelines (a manual run of one is refused server-side), and for a `document` task — every
33
+ // non-document pipeline (per the `purpose` classifier) — see the backend gate.
33
34
  const runMenu = computed(() => {
34
35
  const frame = block.value ? board.serviceOf(block.value) : undefined
35
36
  return pipelines.pipelines
36
- .filter((p) => pipelineAllowedForManualStart(p, frame, board.blocks))
37
+ .filter((p) => pipelineAllowedForManualStart(p, frame, board.blocks, block.value?.taskType))
37
38
  .map((p) => ({
38
39
  label: p.name,
39
40
  icon: 'i-lucide-play',
@@ -1,21 +1,29 @@
1
1
  <script setup lang="ts">
2
2
  import { computed } from 'vue'
3
3
  import { useLocalStorage } from '@vueuse/core'
4
- import type { AgentKind } from '~/types/domain'
4
+ import { purposeAllowsAgentCategory } from '@cat-factory/contracts'
5
+ import type { AgentKind, PipelinePurpose } from '~/types/domain'
5
6
  import { AGENT_CATEGORIES, OBSERVABILITY_GATE_ARCHETYPE } from '~/utils/catalog'
6
7
 
7
8
  const { t } = useI18n()
8
9
  const agents = useAgentsStore()
9
10
  const releaseHealth = useReleaseHealthStore()
10
11
  defineEmits<{ (e: 'add', kind: AgentKind): void }>()
12
+ // The purpose of the pipeline being built. When set to a non-`build` classifier, the
13
+ // Implementation (`build`) and Testing (`test`) categories are hidden — such a pipeline writes
14
+ // no product code and runs no tests (see `purposeAllowsAgentCategory`). `null`/`build` shows all.
15
+ const props = defineProps<{ purpose?: PipelinePurpose | null }>()
11
16
 
12
17
  // The post-release-health gate is only meaningful — and only accepted by the backend —
13
18
  // with an observability integration connected, so it appears in the palette ONLY then.
14
- const palette = computed(() =>
15
- releaseHealth.connection.connected
19
+ const palette = computed(() => {
20
+ const all = releaseHealth.connection.connected
16
21
  ? [...agents.archetypes, OBSERVABILITY_GATE_ARCHETYPE]
17
- : agents.archetypes,
18
- )
22
+ : agents.archetypes
23
+ // Hide the categories the pipeline's purpose doesn't build from (an uncategorized custom kind
24
+ // has no category to gate, so it always shows).
25
+ return all.filter((a) => !a.category || purposeAllowsAgentCategory(props.purpose, a.category))
26
+ })
19
27
 
20
28
  // Group the palette into the ordered catalog categories, plus a trailing "Custom" bucket
21
29
  // for runtime-added agents that carry no category. Empty groups are dropped.
@@ -133,13 +133,14 @@ function setModelPreset(id: string) {
133
133
  const selectedPipeline = computed(() =>
134
134
  props.block.pipelineId ? pipelines.getPipeline(props.block.pipelineId) : undefined,
135
135
  )
136
- // Hide UI-testing pipelines when this task's frame has no UI to exercise, and `'recurring'`-only
137
- // pipelines (the task's manual Run control can't start one) — they'd be refused at run start
138
- // (see utils/pipeline + the backend gate).
136
+ // Hide UI-testing pipelines when this task's frame has no UI to exercise, `'recurring'`-only
137
+ // pipelines (the task's manual Run control can't start one), and for a `document` task every
138
+ // non-document pipeline (it authors a doc, so a build/test pipeline makes no sense). All would be
139
+ // refused / wrong at run start (see utils/pipeline + the backend gate + the purpose classifier).
139
140
  const taskFrame = computed(() => board.serviceOf(props.block))
140
141
  const selectablePipelines = computed(() =>
141
142
  pipelines.pipelines.filter((p) =>
142
- pipelineAllowedForManualStart(p, taskFrame.value, board.blocks),
143
+ pipelineAllowedForManualStart(p, taskFrame.value, board.blocks, props.block.taskType),
143
144
  ),
144
145
  )
145
146
  function setPipeline(id: string) {
@@ -1,6 +1,7 @@
1
1
  <script setup lang="ts">
2
2
  import { computed, ref, watch } from 'vue'
3
- import type { AgentKind, Pipeline } from '~/types/domain'
3
+ import { purposeAllowsAgentCategory } from '@cat-factory/contracts'
4
+ import type { AgentKind, Pipeline, PipelinePurpose } from '~/types/domain'
4
5
  import AgentPalette from '~/components/palettes/AgentPalette.vue'
5
6
  import AgentKindIcon from '~/components/pipeline/AgentKindIcon.vue'
6
7
  import {
@@ -22,6 +23,18 @@ const CONSENSUS_STRATEGIES = computed<{ value: ConsensusStrategy; label: string
22
23
  { value: 'ranked-voting', label: t('pipeline.builder.strategyOption.ranked-voting') },
23
24
  ])
24
25
 
26
+ // The use-case classifier options for the pipeline (its `purpose`). Static literal `t()` keys, one
27
+ // per `PIPELINE_PURPOSES` member, so the typed-message-keys check sees them (a runtime-built key
28
+ // wouldn't be checkable). A non-`build` purpose hides the Implementation/Testing agent kinds in
29
+ // the palette below (see `AgentPalette`), and drives which task pickers offer the saved pipeline.
30
+ const PURPOSE_OPTIONS = computed<{ value: PipelinePurpose; label: string }[]>(() => [
31
+ { value: 'build', label: t('pipeline.builder.purposeOption.build') },
32
+ { value: 'document', label: t('pipeline.builder.purposeOption.document') },
33
+ { value: 'review', label: t('pipeline.builder.purposeOption.review') },
34
+ { value: 'research', label: t('pipeline.builder.purposeOption.research') },
35
+ { value: 'planning', label: t('pipeline.builder.purposeOption.planning') },
36
+ ])
37
+
25
38
  /** Add a blank participant to the draft step's consensus config. */
26
39
  function addParticipant(i: number) {
27
40
  const cfg = pipelines.draftConsensus[i]
@@ -56,6 +69,19 @@ const skillStepNeedsPick = computed(() =>
56
69
  ),
57
70
  )
58
71
 
72
+ // Steps whose agent category the chosen purpose forbids (a non-`build` purpose writes no code and
73
+ // runs no tests, so the Implementation/Testing categories are disallowed — see
74
+ // `purposeAllowsAgentCategory`). The palette hides those kinds, so this is only reachable by
75
+ // switching an existing draft to a non-`build` purpose AFTER such steps were added. The backend has
76
+ // no kind→category map to gate on, so the builder is the enforcement point: save is blocked until
77
+ // the offending steps are removed (or the purpose set back to Build).
78
+ const stepsDisallowedByPurpose = computed(() =>
79
+ pipelines.draft.filter((kind) => {
80
+ const category = agentKindMeta(kind).category
81
+ return !!category && !purposeAllowsAgentCategory(pipelines.draftPurpose, category)
82
+ }),
83
+ )
84
+
59
85
  // A step's picked skill id is no longer in the account catalog (the source dir was renamed or
60
86
  // unlinked). The step will fail cleanly at dispatch; flag it so the user re-picks.
61
87
  function skillMissing(index: number): boolean {
@@ -282,7 +308,7 @@ async function clone(p: Pipeline) {
282
308
  </UButton>
283
309
  </div>
284
310
  <div class="flex-1 pe-1 lg:min-h-0 lg:overflow-y-auto">
285
- <AgentPalette @add="add" />
311
+ <AgentPalette :purpose="pipelines.draftPurpose" @add="add" />
286
312
  </div>
287
313
  </div>
288
314
 
@@ -310,6 +336,24 @@ async function clone(p: Pipeline) {
310
336
  class="mb-2"
311
337
  />
312
338
 
339
+ <!-- Purpose: the pipeline's use-case classifier. Drives which task pickers offer it (a
340
+ document task offers only `document` pipelines) and narrows the palette below (a
341
+ non-build purpose hides the Implementation/Testing kinds). -->
342
+ <div class="mb-2 flex items-center gap-2">
343
+ <label class="shrink-0 text-[11px] font-medium text-slate-400">
344
+ {{ t('pipeline.builder.purposeLabel') }}
345
+ </label>
346
+ <USelect
347
+ :model-value="pipelines.draftPurpose ?? undefined"
348
+ :items="PURPOSE_OPTIONS"
349
+ value-key="value"
350
+ size="sm"
351
+ class="min-w-40"
352
+ :placeholder="t('pipeline.builder.purposePlaceholder')"
353
+ @update:model-value="pipelines.draftPurpose = $event"
354
+ />
355
+ </div>
356
+
313
357
  <!-- Description: the prose summary shown next to the step list in the pipeline pickers. -->
314
358
  <UTextarea
315
359
  v-model="pipelines.draftDescription"
@@ -360,6 +404,14 @@ async function clone(p: Pipeline) {
360
404
  {{ t('pipeline.builder.skillNeedsPick') }}
361
405
  </p>
362
406
 
407
+ <p
408
+ v-if="stepsDisallowedByPurpose.length"
409
+ class="mb-2 flex items-center gap-1.5 rounded-md border border-amber-800/50 bg-amber-950/30 px-2 py-1 text-[11px] text-amber-300"
410
+ >
411
+ <UIcon name="i-lucide-alert-triangle" class="h-3.5 w-3.5 shrink-0" />
412
+ {{ t('pipeline.builder.purposeStepsConflict') }}
413
+ </p>
414
+
363
415
  <div
364
416
  v-if="pipelines.draft.length === 0"
365
417
  class="flex flex-1 items-center justify-center rounded-lg border border-dashed border-slate-700 p-4 text-center text-xs text-slate-500"
@@ -1015,7 +1067,7 @@ async function clone(p: Pipeline) {
1015
1067
  color="primary"
1016
1068
  icon="i-lucide-save"
1017
1069
  size="sm"
1018
- :disabled="pipelines.draft.length === 0"
1070
+ :disabled="pipelines.draft.length === 0 || stepsDisallowedByPurpose.length > 0"
1019
1071
  @click="save"
1020
1072
  >
1021
1073
  {{ pipelines.editingId ? t('pipeline.builder.update') : t('pipeline.builder.save') }}
@@ -2,7 +2,7 @@ import { defineStore } from 'pinia'
2
2
  import { computed, ref } from 'vue'
3
3
  import type { AgentKind, Pipeline } from '~/types/domain'
4
4
  import type { ConsensusStepConfig, StepGating } from '~/types/consensus'
5
- import type { StepOptions, TesterQualityConfig } from '@cat-factory/contracts'
5
+ import type { PipelinePurpose, StepOptions, TesterQualityConfig } from '@cat-factory/contracts'
6
6
  import { companionForProducer, uid } from '~/utils/catalog'
7
7
  import { useUpsertList } from '~/composables/useUpsertList'
8
8
  import { useWorkspaceStore } from '~/stores/workspace'
@@ -81,6 +81,13 @@ export const usePipelinesStore = defineStore('pipelines', () => {
81
81
  const draftStepOptions = ref<(StepOptions | null)[]>([])
82
82
  /** Organizational labels for the pipeline being assembled/edited. */
83
83
  const draftLabels = ref<string[]>([])
84
+ /**
85
+ * The use-case classifier of the pipeline being assembled/edited (`build` / `document` /
86
+ * `review` / `research` / `planning`), or null when unclassified. Drives which task pickers
87
+ * offer the saved pipeline and which agent kinds the builder palette shows (a non-`build`
88
+ * purpose hides the Implementation/Testing kinds).
89
+ */
90
+ const draftPurpose = ref<PipelinePurpose | null>(null)
84
91
  const draftName = ref('New pipeline')
85
92
  /** Prose description for the pipeline being assembled/edited (shown in the pickers). */
86
93
  const draftDescription = ref('')
@@ -323,6 +330,7 @@ export const usePipelinesStore = defineStore('pipelines', () => {
323
330
  draftTesterQuality.value = []
324
331
  draftStepOptions.value = []
325
332
  draftLabels.value = []
333
+ draftPurpose.value = null
326
334
  draftName.value = 'New pipeline'
327
335
  draftDescription.value = ''
328
336
  editingId.value = null
@@ -342,6 +350,7 @@ export const usePipelinesStore = defineStore('pipelines', () => {
342
350
  )
343
351
  draftStepOptions.value = pipeline.agentKinds.map((_, i) => pipeline.stepOptions?.[i] ?? null)
344
352
  draftLabels.value = [...(pipeline.labels ?? [])]
353
+ draftPurpose.value = pipeline.purpose ?? null
345
354
  draftName.value = pipeline.name
346
355
  draftDescription.value = pipeline.description ?? ''
347
356
  editingId.value = pipeline.id
@@ -389,6 +398,10 @@ export const usePipelinesStore = defineStore('pipelines', () => {
389
398
  stepOptions: draftStepOptions.value.map((o) => o ?? null),
390
399
  // Only send labels when there are any.
391
400
  ...(draftLabels.value.length ? { labels: [...draftLabels.value] } : {}),
401
+ // Only send purpose when the pipeline is classified (null ⇒ leave unclassified). Like the
402
+ // legacy per-step arrays, an omitted `purpose` on update reads as "keep existing"; clearing
403
+ // a classification back to unclassified is not a supported edit (every built-in ships one).
404
+ ...(draftPurpose.value ? { purpose: draftPurpose.value } : {}),
392
405
  }
393
406
  }
394
407
 
@@ -459,6 +472,7 @@ export const usePipelinesStore = defineStore('pipelines', () => {
459
472
  draftTesterQuality,
460
473
  draftStepOptions,
461
474
  draftLabels,
475
+ draftPurpose,
462
476
  draftName,
463
477
  draftDescription,
464
478
  editingId,
@@ -60,6 +60,7 @@ export type {
60
60
  AgentCategory,
61
61
  CustomAgentKind,
62
62
  Pipeline,
63
+ PipelinePurpose,
63
64
  SpendStatus,
64
65
  BudgetCaps,
65
66
  Workspace,
@@ -0,0 +1,66 @@
1
+ import { describe, expect, it } from 'vitest'
2
+ import { pipelineAllowedForTaskType, purposeAllowsAgentCategory } from '@cat-factory/contracts'
3
+ import type { Block, Pipeline } from '~/types/domain'
4
+ import { pipelineAllowedForManualStart } from '~/utils/pipeline'
5
+
6
+ // A minimal pipeline: only the fields the launch/task-type filters read matter here.
7
+ function pipeline(over: Partial<Pipeline> = {}): Pipeline {
8
+ return { id: 'pl_x', name: 'X', agentKinds: ['coder'], ...over } as Pipeline
9
+ }
10
+
11
+ describe('pipelineAllowedForTaskType', () => {
12
+ it('a document task offers ONLY document-purpose pipelines', () => {
13
+ expect(pipelineAllowedForTaskType(pipeline({ purpose: 'document' }), 'document')).toBe(true)
14
+ expect(pipelineAllowedForTaskType(pipeline({ purpose: 'build' }), 'document')).toBe(false)
15
+ expect(pipelineAllowedForTaskType(pipeline({ purpose: 'research' }), 'document')).toBe(false)
16
+ // An unclassified pipeline is hidden from a document task (it requires the explicit classifier).
17
+ expect(pipelineAllowedForTaskType(pipeline({ purpose: undefined }), 'document')).toBe(false)
18
+ })
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) {
22
+ expect(pipelineAllowedForTaskType(pipeline({ purpose: 'build' }), type)).toBe(true)
23
+ expect(pipelineAllowedForTaskType(pipeline({ purpose: 'document' }), type)).toBe(true)
24
+ expect(pipelineAllowedForTaskType(pipeline({ purpose: undefined }), type)).toBe(true)
25
+ }
26
+ })
27
+ })
28
+
29
+ describe('purposeAllowsAgentCategory (builder palette gate)', () => {
30
+ it('a build (or unclassified) pipeline may use every category', () => {
31
+ for (const purpose of ['build', null, undefined] as const) {
32
+ for (const cat of ['review', 'design', 'build', 'test', 'docs', 'gates'] as const) {
33
+ expect(purposeAllowsAgentCategory(purpose, cat)).toBe(true)
34
+ }
35
+ }
36
+ })
37
+
38
+ it('a non-build pipeline hides the Implementation (build) and Testing (test) categories', () => {
39
+ for (const purpose of ['document', 'review', 'research', 'planning'] as const) {
40
+ expect(purposeAllowsAgentCategory(purpose, 'build')).toBe(false)
41
+ expect(purposeAllowsAgentCategory(purpose, 'test')).toBe(false)
42
+ // Non-code categories stay visible.
43
+ expect(purposeAllowsAgentCategory(purpose, 'docs')).toBe(true)
44
+ expect(purposeAllowsAgentCategory(purpose, 'review')).toBe(true)
45
+ expect(purposeAllowsAgentCategory(purpose, 'gates')).toBe(true)
46
+ }
47
+ })
48
+ })
49
+
50
+ describe('pipelineAllowedForManualStart composes the task-type gate', () => {
51
+ const noFrame = undefined
52
+ const blocks: Block[] = []
53
+
54
+ it('drops a non-document pipeline for a document task, keeps it for others', () => {
55
+ const build = pipeline({ purpose: 'build' })
56
+ expect(pipelineAllowedForManualStart(build, noFrame, blocks, 'document')).toBe(false)
57
+ expect(pipelineAllowedForManualStart(build, noFrame, blocks, 'feature')).toBe(true)
58
+ // No task type supplied ⇒ no task-type restriction.
59
+ expect(pipelineAllowedForManualStart(build, noFrame, blocks)).toBe(true)
60
+ })
61
+
62
+ it('still excludes recurring-only pipelines regardless of task type', () => {
63
+ const recurring = pipeline({ purpose: 'document', availability: 'recurring' })
64
+ expect(pipelineAllowedForManualStart(recurring, noFrame, blocks, 'document')).toBe(false)
65
+ })
66
+ })
@@ -1,4 +1,8 @@
1
- import { frameAllowsVisualPipeline, pipelineHasVisualStep } from '@cat-factory/contracts'
1
+ import {
2
+ frameAllowsVisualPipeline,
3
+ pipelineAllowedForTaskType,
4
+ pipelineHasVisualStep,
5
+ } from '@cat-factory/contracts'
2
6
  import type { AgentKind, Block, Pipeline } from '~/types/domain'
3
7
 
4
8
  /** One agent step of a pipeline as shown in a preview: its kind + whether it's a human-gated step. */
@@ -25,6 +29,10 @@ export function pipelineDisplaySteps(pipeline: Pipeline): PipelineDisplayStep[]
25
29
  .map(({ kind, gated }) => ({ kind, gated }))
26
30
  }
27
31
 
32
+ // Re-exported so a picker can import the task-type gate from the same module as the
33
+ // launch/frame gates it composes with (the classifier itself lives in `@cat-factory/contracts`).
34
+ export { pipelineAllowedForTaskType }
35
+
28
36
  // Surface counterpart to the backend's slice-4c run-start gate: a pipeline with a visual step
29
37
  // (`tester-ui` / `visual-confirmation`) may run only on a frame with a UI to exercise — a
30
38
  // `frontend` frame, or a frame a `frontend` frame links to. The SPA hides such pipelines from
@@ -53,14 +61,21 @@ export function pipelineAllowedForFrame(
53
61
  /**
54
62
  * Whether `pipeline` may be started as a MANUAL one-off task run (the board/inspector Run menus,
55
63
  * the add-task modal, the task run-settings default). Excludes `'recurring'`-only pipelines the
56
- * backend would refuse.
64
+ * backend would refuse, visual pipelines on a frame with no UI, and — when a `taskType` is given —
65
+ * pipelines whose `purpose` doesn't fit that task type (a `document` task offers only document
66
+ * pipelines). `taskType` omitted ⇒ no task-type restriction (an un-typed context shows all).
57
67
  */
58
68
  export function pipelineAllowedForManualStart(
59
69
  pipeline: Pipeline,
60
70
  frame: Block | undefined,
61
71
  blocks: readonly Block[],
72
+ taskType?: Block['taskType'],
62
73
  ): boolean {
63
- return pipeline.availability !== 'recurring' && pipelineAllowedForFrame(pipeline, frame, blocks)
74
+ return (
75
+ pipeline.availability !== 'recurring' &&
76
+ pipelineAllowedForFrame(pipeline, frame, blocks) &&
77
+ pipelineAllowedForTaskType(pipeline, taskType)
78
+ )
64
79
  }
65
80
 
66
81
  /**
@@ -3142,6 +3142,15 @@
3142
3142
  "impactThreshold": "Auswirkung ≥",
3143
3143
  "strategy": "Strategie",
3144
3144
  "rounds": "Runden",
3145
+ "purposeLabel": "Zweck",
3146
+ "purposePlaceholder": "Zweck auswählen",
3147
+ "purposeOption": {
3148
+ "build": "Entwicklung",
3149
+ "document": "Dokumentation",
3150
+ "review": "Review",
3151
+ "research": "Recherche",
3152
+ "planning": "Planung"
3153
+ },
3145
3154
  "strategyOption": {
3146
3155
  "specialist-panel": "Spezialisten-Panel",
3147
3156
  "debate": "Debatte",
@@ -3195,7 +3204,8 @@
3195
3204
  "skillPlaceholder": "Skill auswählen",
3196
3205
  "skillNoneAvailable": "Keine Skills verfügbar. Verknüpfe eine Skill-Quelle in den Kontoeinstellungen.",
3197
3206
  "skillMissing": "Dieser Skill ist nicht mehr im Katalog; wähle einen anderen.",
3198
- "skillNeedsPick": "Ein Skill-Schritt braucht einen ausgewählten Skill, bevor du speichern kannst."
3207
+ "skillNeedsPick": "Ein Skill-Schritt braucht einen ausgewählten Skill, bevor du speichern kannst.",
3208
+ "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."
3199
3209
  },
3200
3210
  "progress": {
3201
3211
  "status": {
@@ -3508,6 +3508,15 @@
3508
3508
  "impactThreshold": "impact ≥",
3509
3509
  "strategy": "Strategy",
3510
3510
  "rounds": "Rounds",
3511
+ "purposeLabel": "Purpose",
3512
+ "purposePlaceholder": "Select a purpose",
3513
+ "purposeOption": {
3514
+ "build": "Build",
3515
+ "document": "Documentation",
3516
+ "review": "Review",
3517
+ "research": "Research",
3518
+ "planning": "Planning"
3519
+ },
3511
3520
  "strategyOption": {
3512
3521
  "specialist-panel": "Specialist panel",
3513
3522
  "debate": "Debate",
@@ -3561,7 +3570,8 @@
3561
3570
  "skillPlaceholder": "Select a skill",
3562
3571
  "skillNoneAvailable": "No skills available. Link a skill source in account settings.",
3563
3572
  "skillMissing": "This skill is no longer in the catalog; pick another.",
3564
- "skillNeedsPick": "A Skill step needs a skill selected before you can save."
3573
+ "skillNeedsPick": "A Skill step needs a skill selected before you can save.",
3574
+ "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."
3565
3575
  },
3566
3576
  "progress": {
3567
3577
  "status": {
@@ -3409,6 +3409,15 @@
3409
3409
  "impactThreshold": "impacto ≥",
3410
3410
  "strategy": "Estrategia",
3411
3411
  "rounds": "Rondas",
3412
+ "purposeLabel": "Propósito",
3413
+ "purposePlaceholder": "Seleccionar un propósito",
3414
+ "purposeOption": {
3415
+ "build": "Desarrollo",
3416
+ "document": "Documentación",
3417
+ "review": "Revisión",
3418
+ "research": "Investigación",
3419
+ "planning": "Planificación"
3420
+ },
3412
3421
  "strategyOption": {
3413
3422
  "specialist-panel": "Panel de especialistas",
3414
3423
  "debate": "Debate",
@@ -3462,7 +3471,8 @@
3462
3471
  "skillPlaceholder": "Selecciona una habilidad",
3463
3472
  "skillNoneAvailable": "No hay habilidades disponibles. Vincula una fuente de habilidades en la configuración de la cuenta.",
3464
3473
  "skillMissing": "Esta habilidad ya no está en el catálogo; elige otra.",
3465
- "skillNeedsPick": "Un paso Skill necesita una habilidad seleccionada antes de poder guardar."
3474
+ "skillNeedsPick": "Un paso Skill necesita una habilidad seleccionada antes de poder guardar.",
3475
+ "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."
3466
3476
  },
3467
3477
  "progress": {
3468
3478
  "status": {
@@ -3409,6 +3409,15 @@
3409
3409
  "impactThreshold": "impact ≥",
3410
3410
  "strategy": "Stratégie",
3411
3411
  "rounds": "Tours",
3412
+ "purposeLabel": "Objectif",
3413
+ "purposePlaceholder": "Sélectionner un objectif",
3414
+ "purposeOption": {
3415
+ "build": "Développement",
3416
+ "document": "Documentation",
3417
+ "review": "Revue",
3418
+ "research": "Recherche",
3419
+ "planning": "Planification"
3420
+ },
3412
3421
  "strategyOption": {
3413
3422
  "specialist-panel": "Panel de spécialistes",
3414
3423
  "debate": "Débat",
@@ -3462,7 +3471,8 @@
3462
3471
  "skillPlaceholder": "Sélectionner une compétence",
3463
3472
  "skillNoneAvailable": "Aucune compétence disponible. Reliez une source de compétences dans les paramètres du compte.",
3464
3473
  "skillMissing": "Cette compétence n'est plus dans le catalogue ; choisissez-en une autre.",
3465
- "skillNeedsPick": "Une étape Skill a besoin d'une compétence sélectionnée avant de pouvoir enregistrer."
3474
+ "skillNeedsPick": "Une étape Skill a besoin d'une compétence sélectionnée avant de pouvoir enregistrer.",
3475
+ "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."
3466
3476
  },
3467
3477
  "progress": {
3468
3478
  "status": {
@@ -3420,6 +3420,15 @@
3420
3420
  "impactThreshold": "השפעה ≥",
3421
3421
  "strategy": "אסטרטגיה",
3422
3422
  "rounds": "סבבים",
3423
+ "purposeLabel": "מטרה",
3424
+ "purposePlaceholder": "בחר מטרה",
3425
+ "purposeOption": {
3426
+ "build": "פיתוח",
3427
+ "document": "תיעוד",
3428
+ "review": "סקירה",
3429
+ "research": "מחקר",
3430
+ "planning": "תכנון"
3431
+ },
3423
3432
  "strategyOption": {
3424
3433
  "specialist-panel": "פאנל מומחים",
3425
3434
  "debate": "דיון",
@@ -3473,7 +3482,8 @@
3473
3482
  "skillPlaceholder": "בחר כישור",
3474
3483
  "skillNoneAvailable": "אין כישורים זמינים. קישר מקור כישורים בהגדרות החשבון.",
3475
3484
  "skillMissing": "כישור זה כבר אינו בקטלוג; בחר אחר.",
3476
- "skillNeedsPick": "שלב Skill דורש כישור נבחר לפני שתוכל לשמור."
3485
+ "skillNeedsPick": "שלב Skill דורש כישור נבחר לפני שתוכל לשמור.",
3486
+ "purposeStepsConflict": "מטרה זו אינה כותבת קוד ואינה מריצה בדיקות, אך הצינור עדיין כולל שלבי פיתוח או בדיקה. הסירו אותם או שנו את המטרה לפיתוח."
3477
3487
  },
3478
3488
  "progress": {
3479
3489
  "status": {
@@ -3142,6 +3142,15 @@
3142
3142
  "impactThreshold": "impatto ≥",
3143
3143
  "strategy": "Strategia",
3144
3144
  "rounds": "Giri",
3145
+ "purposeLabel": "Scopo",
3146
+ "purposePlaceholder": "Seleziona uno scopo",
3147
+ "purposeOption": {
3148
+ "build": "Sviluppo",
3149
+ "document": "Documentazione",
3150
+ "review": "Revisione",
3151
+ "research": "Ricerca",
3152
+ "planning": "Pianificazione"
3153
+ },
3145
3154
  "strategyOption": {
3146
3155
  "specialist-panel": "Panel di specialisti",
3147
3156
  "debate": "Dibattito",
@@ -3195,7 +3204,8 @@
3195
3204
  "skillPlaceholder": "Seleziona una competenza",
3196
3205
  "skillNoneAvailable": "Nessuna competenza disponibile. Collega una fonte di competenze nelle impostazioni dell'account.",
3197
3206
  "skillMissing": "Questa competenza non è più nel catalogo; scegline un'altra.",
3198
- "skillNeedsPick": "Un passo Skill richiede una competenza selezionata prima di poter salvare."
3207
+ "skillNeedsPick": "Un passo Skill richiede una competenza selezionata prima di poter salvare.",
3208
+ "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."
3199
3209
  },
3200
3210
  "progress": {
3201
3211
  "status": {
@@ -3421,6 +3421,15 @@
3421
3421
  "impactThreshold": "影響度 ≥",
3422
3422
  "strategy": "戦略",
3423
3423
  "rounds": "ラウンド",
3424
+ "purposeLabel": "目的",
3425
+ "purposePlaceholder": "目的を選択",
3426
+ "purposeOption": {
3427
+ "build": "開発",
3428
+ "document": "ドキュメント",
3429
+ "review": "レビュー",
3430
+ "research": "調査",
3431
+ "planning": "計画"
3432
+ },
3424
3433
  "strategyOption": {
3425
3434
  "specialist-panel": "専門家パネル",
3426
3435
  "debate": "ディベート",
@@ -3474,7 +3483,8 @@
3474
3483
  "skillPlaceholder": "スキルを選択",
3475
3484
  "skillNoneAvailable": "利用できるスキルがありません。アカウント設定でスキルソースをリンクしてください。",
3476
3485
  "skillMissing": "このスキルはカタログに存在しません。別のスキルを選んでください。",
3477
- "skillNeedsPick": "Skill ステップを保存するにはスキルの選択が必要です。"
3486
+ "skillNeedsPick": "Skill ステップを保存するにはスキルの選択が必要です。",
3487
+ "purposeStepsConflict": "この目的はコードを書かず、テストも実行しませんが、パイプラインには実装またはテストのステップが残っています。それらを削除するか、目的を「開発」に切り替えてください。"
3478
3488
  },
3479
3489
  "progress": {
3480
3490
  "status": {
@@ -3409,6 +3409,15 @@
3409
3409
  "impactThreshold": "wpływ ≥",
3410
3410
  "strategy": "Strategia",
3411
3411
  "rounds": "Rundy",
3412
+ "purposeLabel": "Cel",
3413
+ "purposePlaceholder": "Wybierz cel",
3414
+ "purposeOption": {
3415
+ "build": "Programowanie",
3416
+ "document": "Dokumentacja",
3417
+ "review": "Przegląd",
3418
+ "research": "Badania",
3419
+ "planning": "Planowanie"
3420
+ },
3412
3421
  "strategyOption": {
3413
3422
  "specialist-panel": "Panel specjalistów",
3414
3423
  "debate": "Debata",
@@ -3462,7 +3471,8 @@
3462
3471
  "skillPlaceholder": "Wybierz umiejętność",
3463
3472
  "skillNoneAvailable": "Brak dostępnych umiejętności. Połącz źródło umiejętności w ustawieniach konta.",
3464
3473
  "skillMissing": "Tej umiejętności nie ma już w katalogu; wybierz inną.",
3465
- "skillNeedsPick": "Krok Skill wymaga wybranej umiejętności, zanim będzie można zapisać."
3474
+ "skillNeedsPick": "Krok Skill wymaga wybranej umiejętności, zanim będzie można zapisać.",
3475
+ "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."
3466
3476
  },
3467
3477
  "progress": {
3468
3478
  "status": {
@@ -3421,6 +3421,15 @@
3421
3421
  "impactThreshold": "etki ≥",
3422
3422
  "strategy": "Strateji",
3423
3423
  "rounds": "Tur",
3424
+ "purposeLabel": "Amaç",
3425
+ "purposePlaceholder": "Bir amaç seçin",
3426
+ "purposeOption": {
3427
+ "build": "Geliştirme",
3428
+ "document": "Dokümantasyon",
3429
+ "review": "İnceleme",
3430
+ "research": "Araştırma",
3431
+ "planning": "Planlama"
3432
+ },
3424
3433
  "strategyOption": {
3425
3434
  "specialist-panel": "Uzman paneli",
3426
3435
  "debate": "Tartışma",
@@ -3474,7 +3483,8 @@
3474
3483
  "skillPlaceholder": "Bir beceri seçin",
3475
3484
  "skillNoneAvailable": "Kullanılabilir beceri yok. Hesap ayarlarında bir beceri kaynağı bağlayın.",
3476
3485
  "skillMissing": "Bu beceri artık katalogda yok; başka birini seçin.",
3477
- "skillNeedsPick": "Bir Skill adımı, kaydedebilmeniz için seçilmiş bir beceri gerektirir."
3486
+ "skillNeedsPick": "Bir Skill adımı, kaydedebilmeniz için seçilmiş bir beceri gerektirir.",
3487
+ "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."
3478
3488
  },
3479
3489
  "progress": {
3480
3490
  "status": {
@@ -3409,6 +3409,15 @@
3409
3409
  "impactThreshold": "вплив ≥",
3410
3410
  "strategy": "Стратегія",
3411
3411
  "rounds": "Раунди",
3412
+ "purposeLabel": "Призначення",
3413
+ "purposePlaceholder": "Виберіть призначення",
3414
+ "purposeOption": {
3415
+ "build": "Розробка",
3416
+ "document": "Документація",
3417
+ "review": "Рецензування",
3418
+ "research": "Дослідження",
3419
+ "planning": "Планування"
3420
+ },
3412
3421
  "strategyOption": {
3413
3422
  "specialist-panel": "Панель фахівців",
3414
3423
  "debate": "Дебати",
@@ -3462,7 +3471,8 @@
3462
3471
  "skillPlaceholder": "Виберіть навичку",
3463
3472
  "skillNoneAvailable": "Немає доступних навичок. Пов'яжіть джерело навичок у налаштуваннях облікового запису.",
3464
3473
  "skillMissing": "Цієї навички більше немає в каталозі; виберіть іншу.",
3465
- "skillNeedsPick": "Крок Skill потребує вибраної навички, перш ніж ви зможете зберегти."
3474
+ "skillNeedsPick": "Крок Skill потребує вибраної навички, перш ніж ви зможете зберегти.",
3475
+ "purposeStepsConflict": "Це призначення не пише код і не запускає тести, але конвеєр досі містить кроки програмування або тестування. Видаліть їх або змініть призначення на Розробку."
3466
3476
  },
3467
3477
  "progress": {
3468
3478
  "status": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cat-factory/app",
3
- "version": "0.140.0",
3
+ "version": "0.141.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.149.0"
43
+ "@cat-factory/contracts": "0.150.0"
44
44
  },
45
45
  "devDependencies": {
46
46
  "@toad-contracts/testing": "0.3.2",