@cat-factory/app 0.119.0 → 0.120.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.
@@ -102,6 +102,13 @@ const isRecurring = computed(() => taskType.value === 'recurring')
102
102
  const severity = ref<'low' | 'medium' | 'high' | 'critical' | ''>('')
103
103
  const stepsToReproduce = ref('')
104
104
  const timeboxHours = ref<number | undefined>(undefined)
105
+ // Spike research criteria — folded into the spike agent's prompt (see the backend `spike` kind).
106
+ const spikeResearchQuestion = ref('')
107
+ const spikeSuccessCriteria = ref('')
108
+ const spikeOptionsToCompare = ref('')
109
+ // Optional in-repo path the findings document is committed to (else `docs/research/<slug>.md`);
110
+ // shares the `taskTypeFields.targetPath` field + its safe-`.md`-path validation with `document`.
111
+ const spikeTargetPath = ref('')
105
112
  // `DOC_KINDS` (and the `DocKind` type) are owned by the contracts package — re-exported via
106
113
  // `~/types/domain` — so the picker and the create payload can't drift from the backend list.
107
114
  const docKind = ref<DocKind | ''>('')
@@ -171,13 +178,21 @@ function buildTypeFields(): TaskTypeFields | undefined {
171
178
  return Object.keys(f).length ? f : undefined
172
179
  }
173
180
  if (taskType.value === 'spike') {
181
+ const f: TaskTypeFields = {}
174
182
  // `v-model.number` on a cleared number input yields '' (not undefined), which would
175
183
  // serialise as a non-number and 400 the create — so require a finite number here.
176
- return typeof timeboxHours.value === 'number' &&
184
+ if (
185
+ typeof timeboxHours.value === 'number' &&
177
186
  Number.isFinite(timeboxHours.value) &&
178
187
  timeboxHours.value >= 0
179
- ? { timeboxHours: timeboxHours.value }
180
- : undefined
188
+ ) {
189
+ f.timeboxHours = timeboxHours.value
190
+ }
191
+ if (spikeResearchQuestion.value.trim()) f.researchQuestion = spikeResearchQuestion.value.trim()
192
+ if (spikeSuccessCriteria.value.trim()) f.successCriteria = spikeSuccessCriteria.value.trim()
193
+ if (spikeOptionsToCompare.value.trim()) f.optionsToCompare = spikeOptionsToCompare.value.trim()
194
+ if (spikeTargetPath.value.trim()) f.targetPath = spikeTargetPath.value.trim()
195
+ return Object.keys(f).length ? f : undefined
181
196
  }
182
197
  if (taskType.value === 'document') {
183
198
  const f: TaskTypeFields = {}
@@ -406,6 +421,10 @@ watch(open, (isOpen) => {
406
421
  severity.value = ''
407
422
  stepsToReproduce.value = ''
408
423
  timeboxHours.value = undefined
424
+ spikeResearchQuestion.value = ''
425
+ spikeSuccessCriteria.value = ''
426
+ spikeOptionsToCompare.value = ''
427
+ spikeTargetPath.value = ''
409
428
  docKind.value = ''
410
429
  docAudience.value = ''
411
430
  docTargetPath.value = ''
@@ -451,6 +470,10 @@ const { requestClose } = useUnsavedGuard({
451
470
  severity: severity.value,
452
471
  stepsToReproduce: stepsToReproduce.value.trim(),
453
472
  timeboxHours: timeboxHours.value ?? null,
473
+ spikeResearchQuestion: spikeResearchQuestion.value.trim(),
474
+ spikeSuccessCriteria: spikeSuccessCriteria.value.trim(),
475
+ spikeOptionsToCompare: spikeOptionsToCompare.value.trim(),
476
+ spikeTargetPath: spikeTargetPath.value.trim(),
454
477
  docKind: docKind.value,
455
478
  docAudience: docAudience.value.trim(),
456
479
  docTargetPath: docTargetPath.value.trim(),
@@ -671,15 +694,58 @@ async function add() {
671
694
  </UFormField>
672
695
  </div>
673
696
 
674
- <UFormField v-else-if="taskType === 'spike'" :label="t('board.addTask.timebox')">
675
- <UInput
676
- v-model.number="timeboxHours"
677
- type="number"
678
- min="0"
679
- :placeholder="t('board.addTask.timeboxPlaceholder')"
680
- class="w-full"
681
- />
682
- </UFormField>
697
+ <div v-else-if="taskType === 'spike'" class="space-y-3">
698
+ <UFormField :label="t('board.addTask.timebox')">
699
+ <UInput
700
+ v-model.number="timeboxHours"
701
+ type="number"
702
+ min="0"
703
+ :placeholder="t('board.addTask.timeboxPlaceholder')"
704
+ class="w-full"
705
+ />
706
+ </UFormField>
707
+ <UFormField
708
+ :label="t('board.addTask.spikeFields.researchQuestion.label')"
709
+ :hint="t('board.addTask.optional')"
710
+ >
711
+ <UInput
712
+ v-model="spikeResearchQuestion"
713
+ :placeholder="t('board.addTask.spikeFields.researchQuestion.placeholder')"
714
+ class="w-full"
715
+ />
716
+ </UFormField>
717
+ <UFormField
718
+ :label="t('board.addTask.spikeFields.successCriteria.label')"
719
+ :hint="t('board.addTask.optional')"
720
+ >
721
+ <UTextarea
722
+ v-model="spikeSuccessCriteria"
723
+ :rows="2"
724
+ autoresize
725
+ :placeholder="t('board.addTask.spikeFields.successCriteria.placeholder')"
726
+ class="w-full"
727
+ />
728
+ </UFormField>
729
+ <UFormField
730
+ :label="t('board.addTask.spikeFields.optionsToCompare.label')"
731
+ :hint="t('board.addTask.optional')"
732
+ >
733
+ <UTextarea
734
+ v-model="spikeOptionsToCompare"
735
+ :rows="2"
736
+ autoresize
737
+ :placeholder="t('board.addTask.spikeFields.optionsToCompare.placeholder')"
738
+ class="w-full"
739
+ />
740
+ </UFormField>
741
+ <UFormField :label="t('board.addTask.targetPath')" :hint="t('board.addTask.optional')">
742
+ <UInput
743
+ v-model="spikeTargetPath"
744
+ :placeholder="t('board.addTask.targetPathPlaceholder')"
745
+ class="w-full"
746
+ />
747
+ </UFormField>
748
+ </div>
683
749
 
684
750
  <div v-else-if="taskType === 'document'" class="space-y-3">
685
751
  <UFormField :label="t('board.addTask.documentKind')">
@@ -17,6 +17,7 @@ const AGENT_KINDS: AgentKind[] = [
17
17
  'requirements-brainstorm',
18
18
  'architecture-brainstorm',
19
19
  'bug-investigator',
20
+ 'spike',
20
21
  'task-estimator',
21
22
  'architect',
22
23
  'researcher',
@@ -64,6 +64,19 @@ export const AGENT_ARCHETYPES: AgentArchetype[] = [
64
64
  'Read-only, multi-repo codebase investigation that traces the bug to its root cause and decides whether the report is fixable as-is or needs the reporter to clarify (no code changes).',
65
65
  resultView: 'generic-structured',
66
66
  },
67
+ {
68
+ // A timeboxed read-only research/investigation agent. Its structured findings open in the
69
+ // shared generic viewer; the findings document is committed by a backend post-op (delivered
70
+ // as a pull request by default, or straight to base via the direct pipeline).
71
+ kind: 'spike',
72
+ label: 'Spike',
73
+ icon: 'i-lucide-flask-conical',
74
+ color: '#22d3ee',
75
+ category: 'design',
76
+ description:
77
+ 'Timeboxed read-only investigation that answers a research question against the context and codebase, and delivers a findings document (as a pull request by default; no code).',
78
+ resultView: 'generic-structured',
79
+ },
67
80
  {
68
81
  kind: 'task-estimator',
69
82
  label: 'Task Estimator',
@@ -2064,6 +2064,20 @@
2064
2064
  "placeholder": "Die Endpoints oder die Oberfläche im Geltungsbereich"
2065
2065
  }
2066
2066
  },
2067
+ "spikeFields": {
2068
+ "researchQuestion": {
2069
+ "label": "Forschungsfrage",
2070
+ "placeholder": "Die Frage oder Hypothese, die der Spike beantworten soll"
2071
+ },
2072
+ "successCriteria": {
2073
+ "label": "Erfolgskriterien",
2074
+ "placeholder": "Die Akzeptanzkriterien oder die Entscheidung, die die Ergebnisse stützen sollen"
2075
+ },
2076
+ "optionsToCompare": {
2077
+ "label": "Zu vergleichende Optionen",
2078
+ "placeholder": "Die gegeneinander abzuwägenden Optionen"
2079
+ }
2080
+ },
2067
2081
  "optional": "optional",
2068
2082
  "pipeline": "Pipeline",
2069
2083
  "chooseAtRunTime": "Zur Laufzeit auswählen",
@@ -258,6 +258,20 @@
258
258
  "placeholder": "The endpoints or surface in scope"
259
259
  }
260
260
  },
261
+ "spikeFields": {
262
+ "researchQuestion": {
263
+ "label": "Research question",
264
+ "placeholder": "The question or hypothesis the spike must answer"
265
+ },
266
+ "successCriteria": {
267
+ "label": "Success criteria",
268
+ "placeholder": "The acceptance criteria or the decision the findings must inform"
269
+ },
270
+ "optionsToCompare": {
271
+ "label": "Options to compare",
272
+ "placeholder": "The options to weigh against each other"
273
+ }
274
+ },
261
275
  "optional": "optional",
262
276
  "pipeline": "Pipeline",
263
277
  "chooseAtRunTime": "Choose at run time",
@@ -240,6 +240,20 @@
240
240
  "placeholder": "Los endpoints o la superficie dentro del alcance"
241
241
  }
242
242
  },
243
+ "spikeFields": {
244
+ "researchQuestion": {
245
+ "label": "Pregunta de investigación",
246
+ "placeholder": "La pregunta o hipótesis que el spike debe responder"
247
+ },
248
+ "successCriteria": {
249
+ "label": "Criterios de éxito",
250
+ "placeholder": "Los criterios de aceptación o la decisión que deben fundamentar los hallazgos"
251
+ },
252
+ "optionsToCompare": {
253
+ "label": "Opciones a comparar",
254
+ "placeholder": "Las opciones a sopesar entre sí"
255
+ }
256
+ },
243
257
  "optional": "opcional",
244
258
  "pipeline": "Pipeline",
245
259
  "chooseAtRunTime": "Elegir al ejecutar",
@@ -240,6 +240,20 @@
240
240
  "placeholder": "Les endpoints ou la surface concernés"
241
241
  }
242
242
  },
243
+ "spikeFields": {
244
+ "researchQuestion": {
245
+ "label": "Question de recherche",
246
+ "placeholder": "La question ou l'hypothèse à laquelle le spike doit répondre"
247
+ },
248
+ "successCriteria": {
249
+ "label": "Critères de réussite",
250
+ "placeholder": "Les critères d'acceptation ou la décision que les conclusions doivent éclairer"
251
+ },
252
+ "optionsToCompare": {
253
+ "label": "Options à comparer",
254
+ "placeholder": "Les options à mettre en balance"
255
+ }
256
+ },
243
257
  "optional": "optionnel",
244
258
  "pipeline": "Pipeline",
245
259
  "chooseAtRunTime": "Choisir au lancement",
@@ -240,6 +240,20 @@
240
240
  "placeholder": "נקודות הקצה או המשטח שבתחום"
241
241
  }
242
242
  },
243
+ "spikeFields": {
244
+ "researchQuestion": {
245
+ "label": "שאלת מחקר",
246
+ "placeholder": "השאלה או ההשערה שעל הספייק לענות עליה"
247
+ },
248
+ "successCriteria": {
249
+ "label": "קריטריוני הצלחה",
250
+ "placeholder": "קריטריוני הקבלה או ההחלטה שהממצאים צריכים לבסס"
251
+ },
252
+ "optionsToCompare": {
253
+ "label": "אפשרויות להשוואה",
254
+ "placeholder": "האפשרויות שיש לשקול זו מול זו"
255
+ }
256
+ },
243
257
  "optional": "אופציונלי",
244
258
  "pipeline": "צינור",
245
259
  "chooseAtRunTime": "בחר בזמן הריצה",
@@ -2064,6 +2064,20 @@
2064
2064
  "placeholder": "Gli endpoint o la superficie in ambito"
2065
2065
  }
2066
2066
  },
2067
+ "spikeFields": {
2068
+ "researchQuestion": {
2069
+ "label": "Domanda di ricerca",
2070
+ "placeholder": "La domanda o l'ipotesi a cui lo spike deve rispondere"
2071
+ },
2072
+ "successCriteria": {
2073
+ "label": "Criteri di successo",
2074
+ "placeholder": "I criteri di accettazione o la decisione che i risultati devono supportare"
2075
+ },
2076
+ "optionsToCompare": {
2077
+ "label": "Opzioni da confrontare",
2078
+ "placeholder": "Le opzioni da valutare l'una rispetto all'altra"
2079
+ }
2080
+ },
2067
2081
  "optional": "opzionale",
2068
2082
  "pipeline": "Pipeline",
2069
2083
  "chooseAtRunTime": "Scegli al momento dell'esecuzione",
@@ -240,6 +240,20 @@
240
240
  "placeholder": "対象となるエンドポイントや範囲"
241
241
  }
242
242
  },
243
+ "spikeFields": {
244
+ "researchQuestion": {
245
+ "label": "調査課題",
246
+ "placeholder": "スパイクで答えるべき問いや仮説"
247
+ },
248
+ "successCriteria": {
249
+ "label": "成功基準",
250
+ "placeholder": "受け入れ基準、または調査結果が判断材料とすべき決定事項"
251
+ },
252
+ "optionsToCompare": {
253
+ "label": "比較する選択肢",
254
+ "placeholder": "互いに比較検討する選択肢"
255
+ }
256
+ },
243
257
  "optional": "任意",
244
258
  "pipeline": "パイプライン",
245
259
  "chooseAtRunTime": "実行時に選択",
@@ -240,6 +240,20 @@
240
240
  "placeholder": "Endpointy lub zakres objęty dokumentem"
241
241
  }
242
242
  },
243
+ "spikeFields": {
244
+ "researchQuestion": {
245
+ "label": "Pytanie badawcze",
246
+ "placeholder": "Pytanie lub hipoteza, na które ma odpowiedzieć spike"
247
+ },
248
+ "successCriteria": {
249
+ "label": "Kryteria sukcesu",
250
+ "placeholder": "Kryteria akceptacji lub decyzja, którą mają uzasadnić wyniki"
251
+ },
252
+ "optionsToCompare": {
253
+ "label": "Opcje do porównania",
254
+ "placeholder": "Opcje do rozważenia względem siebie"
255
+ }
256
+ },
243
257
  "optional": "opcjonalne",
244
258
  "pipeline": "Pipeline",
245
259
  "chooseAtRunTime": "Wybierz przy uruchomieniu",
@@ -240,6 +240,20 @@
240
240
  "placeholder": "Kapsamdaki uç noktalar veya yüzey"
241
241
  }
242
242
  },
243
+ "spikeFields": {
244
+ "researchQuestion": {
245
+ "label": "Araştırma sorusu",
246
+ "placeholder": "Spike'ın yanıtlaması gereken soru veya hipotez"
247
+ },
248
+ "successCriteria": {
249
+ "label": "Başarı ölçütleri",
250
+ "placeholder": "Kabul ölçütleri veya bulguların dayanak oluşturması gereken karar"
251
+ },
252
+ "optionsToCompare": {
253
+ "label": "Karşılaştırılacak seçenekler",
254
+ "placeholder": "Birbirine karşı tartılacak seçenekler"
255
+ }
256
+ },
243
257
  "optional": "isteğe bağlı",
244
258
  "pipeline": "İşlem hattı",
245
259
  "chooseAtRunTime": "Çalıştırma sırasında seç",
@@ -240,6 +240,20 @@
240
240
  "placeholder": "Кінцеві точки або поверхня в межах обсягу"
241
241
  }
242
242
  },
243
+ "spikeFields": {
244
+ "researchQuestion": {
245
+ "label": "Дослідницьке питання",
246
+ "placeholder": "Питання або гіпотеза, на які має відповісти спайк"
247
+ },
248
+ "successCriteria": {
249
+ "label": "Критерії успіху",
250
+ "placeholder": "Критерії приймання або рішення, яке мають обґрунтувати висновки"
251
+ },
252
+ "optionsToCompare": {
253
+ "label": "Опції для порівняння",
254
+ "placeholder": "Опції, які слід зважити одна проти одної"
255
+ }
256
+ },
243
257
  "optional": "необов’язково",
244
258
  "pipeline": "Конвеєр",
245
259
  "chooseAtRunTime": "Обрати під час запуску",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cat-factory/app",
3
- "version": "0.119.0",
3
+ "version": "0.120.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",
@@ -34,7 +34,7 @@
34
34
  "valibot": "^1.4.2",
35
35
  "vue": "3.5.39",
36
36
  "wretch": "^3.0.9",
37
- "@cat-factory/contracts": "0.132.0"
37
+ "@cat-factory/contracts": "0.134.0"
38
38
  },
39
39
  "devDependencies": {
40
40
  "@toad-contracts/testing": "0.3.2",