@cat-factory/app 0.9.1 → 0.11.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.
Files changed (32) hide show
  1. package/app/components/board/AddTaskModal.vue +209 -21
  2. package/app/components/board/nodes/BlockNode.vue +2 -2
  3. package/app/components/focus/BlockFocusView.vue +2 -2
  4. package/app/components/layout/AccountTeamSettings.vue +6 -3
  5. package/app/components/layout/CommandBar.vue +7 -33
  6. package/app/components/layout/IntegrationsHub.vue +230 -0
  7. package/app/components/layout/SideBar.vue +8 -170
  8. package/app/components/panels/GenericStructuredResultView.vue +131 -0
  9. package/app/components/panels/InspectorPanel.vue +6 -2
  10. package/app/components/panels/StepResultViewHost.vue +4 -0
  11. package/app/components/panels/inspector/ServiceReleaseHealthConfig.vue +148 -0
  12. package/app/components/providers/ApiKeysSection.vue +67 -26
  13. package/app/components/providers/VendorCredentialsModal.vue +115 -84
  14. package/app/components/settings/IssueTrackerWritebackPanel.vue +45 -57
  15. package/app/components/settings/MergeThresholdsPanel.vue +189 -226
  16. package/app/components/settings/ObservabilityConnectionPanel.vue +151 -0
  17. package/app/components/settings/ServiceFragmentDefaultsPanel.vue +46 -61
  18. package/app/components/settings/WorkspaceSettingsPanel.vue +136 -63
  19. package/app/composables/api/releaseHealth.ts +11 -10
  20. package/app/pages/index.vue +4 -8
  21. package/app/stores/agents.ts +27 -2
  22. package/app/stores/releaseHealth.ts +48 -12
  23. package/app/stores/ui.ts +34 -42
  24. package/app/stores/workspace.ts +4 -0
  25. package/app/types/domain.ts +33 -1
  26. package/app/types/execution.ts +6 -0
  27. package/app/types/releaseHealth.ts +19 -11
  28. package/app/utils/catalog.spec.ts +10 -0
  29. package/app/utils/catalog.ts +20 -6
  30. package/package.json +1 -1
  31. package/app/components/board/ContextPicker.vue +0 -367
  32. package/app/components/settings/DatadogPanel.vue +0 -213
@@ -15,15 +15,9 @@ const CONCERN_LEVELS: { value: RequirementConcernLevel; label: string }[] = [
15
15
  { value: 'high', label: 'High (never stop)' },
16
16
  ]
17
17
 
18
- const ui = useUiStore()
19
18
  const store = useMergePresetsStore()
20
19
  const toast = useToast()
21
20
 
22
- const open = computed({
23
- get: () => ui.mergeThresholdsOpen,
24
- set: (v: boolean) => (v ? ui.openMergeThresholds() : ui.closeMergeThresholds()),
25
- })
26
-
27
21
  // Local editable copy per preset, kept in sync with the store. Percentages are
28
22
  // edited 0..100 and stored 0..1.
29
23
  interface Draft {
@@ -149,230 +143,199 @@ async function create() {
149
143
  </script>
150
144
 
151
145
  <template>
152
- <UModal v-model:open="open" title="Merge thresholds" :ui="{ content: 'max-w-2xl' }">
153
- <template #body>
154
- <div class="space-y-4">
155
- <p class="text-xs text-slate-400">
156
- Named auto-merge policies a task can choose. After CI passes, the
157
- <span class="text-slate-300">merger</span> agent scores the PR on complexity, risk and
158
- impact (0–100%); the PR auto-merges only when every score is at or below the preset's
159
- ceilings — otherwise a review notification is raised. The default preset governs any task
160
- that picks none.
161
- </p>
146
+ <div class="space-y-4">
147
+ <p class="text-xs text-slate-400">
148
+ Named auto-merge policies a task can choose. After CI passes, the
149
+ <span class="text-slate-300">merger</span> agent scores the PR on complexity, risk and impact
150
+ (0–100%); the PR auto-merges only when every score is at or below the preset's ceilings —
151
+ otherwise a review notification is raised. The default preset governs any task that picks
152
+ none.
153
+ </p>
162
154
 
163
- <div
164
- v-for="p in store.presets"
165
- :key="p.id"
166
- class="rounded-lg border border-slate-700 bg-slate-800/40 p-3"
155
+ <div
156
+ v-for="p in store.presets"
157
+ :key="p.id"
158
+ class="rounded-lg border border-slate-700 bg-slate-800/40 p-3"
159
+ >
160
+ <div class="mb-3 flex items-center gap-2">
161
+ <UInput v-model="drafts[p.id]!.name" size="sm" class="flex-1" placeholder="Preset name" />
162
+ <UBadge v-if="p.isDefault" color="primary" variant="subtle" size="sm">Default</UBadge>
163
+ <UButton
164
+ v-else
165
+ color="neutral"
166
+ variant="ghost"
167
+ size="xs"
168
+ icon="i-lucide-star"
169
+ :loading="busy === p.id"
170
+ @click="makeDefault(p)"
167
171
  >
168
- <div class="mb-3 flex items-center gap-2">
169
- <UInput
170
- v-model="drafts[p.id]!.name"
171
- size="sm"
172
- class="flex-1"
173
- placeholder="Preset name"
174
- />
175
- <UBadge v-if="p.isDefault" color="primary" variant="subtle" size="sm">Default</UBadge>
176
- <UButton
177
- v-else
178
- color="neutral"
179
- variant="ghost"
180
- size="xs"
181
- icon="i-lucide-star"
182
- :loading="busy === p.id"
183
- @click="makeDefault(p)"
184
- >
185
- Make default
186
- </UButton>
187
- <UButton
188
- color="error"
189
- variant="ghost"
190
- size="xs"
191
- icon="i-lucide-trash-2"
192
- :disabled="p.isDefault || busy === p.id"
193
- :title="p.isDefault ? 'The default preset cannot be deleted' : 'Delete preset'"
194
- @click="remove(p)"
195
- />
196
- </div>
172
+ Make default
173
+ </UButton>
174
+ <UButton
175
+ color="error"
176
+ variant="ghost"
177
+ size="xs"
178
+ icon="i-lucide-trash-2"
179
+ :disabled="p.isDefault || busy === p.id"
180
+ :title="p.isDefault ? 'The default preset cannot be deleted' : 'Delete preset'"
181
+ @click="remove(p)"
182
+ />
183
+ </div>
197
184
 
198
- <div class="grid grid-cols-2 gap-3 sm:grid-cols-4">
199
- <label class="block">
200
- <span class="mb-1 block text-[10px] uppercase tracking-wide text-slate-500">
201
- Max complexity %
202
- </span>
203
- <UInput
204
- v-model.number="drafts[p.id]!.maxComplexity"
205
- type="number"
206
- :min="0"
207
- :max="100"
208
- size="sm"
209
- />
210
- </label>
211
- <label class="block">
212
- <span class="mb-1 block text-[10px] uppercase tracking-wide text-slate-500">
213
- Max risk %
214
- </span>
215
- <UInput
216
- v-model.number="drafts[p.id]!.maxRisk"
217
- type="number"
218
- :min="0"
219
- :max="100"
220
- size="sm"
221
- />
222
- </label>
223
- <label class="block">
224
- <span class="mb-1 block text-[10px] uppercase tracking-wide text-slate-500">
225
- Max impact %
226
- </span>
227
- <UInput
228
- v-model.number="drafts[p.id]!.maxImpact"
229
- type="number"
230
- :min="0"
231
- :max="100"
232
- size="sm"
233
- />
234
- </label>
235
- <label class="block">
236
- <span class="mb-1 block text-[10px] uppercase tracking-wide text-slate-500">
237
- CI-fix attempts
238
- </span>
239
- <UInput
240
- v-model.number="drafts[p.id]!.ciMaxAttempts"
241
- type="number"
242
- :min="0"
243
- :max="50"
244
- size="sm"
245
- />
246
- </label>
247
- <label class="block">
248
- <span class="mb-1 block text-[10px] uppercase tracking-wide text-slate-500">
249
- Requirement iterations
250
- </span>
251
- <UInput
252
- v-model.number="drafts[p.id]!.maxRequirementIterations"
253
- type="number"
254
- :min="1"
255
- :max="20"
256
- size="sm"
257
- />
258
- </label>
259
- <label class="block">
260
- <span class="mb-1 block text-[10px] uppercase tracking-wide text-slate-500">
261
- Auto-pass concerns ≤
262
- </span>
263
- <USelect
264
- v-model="drafts[p.id]!.maxRequirementConcernAllowed"
265
- :items="CONCERN_LEVELS"
266
- value-key="value"
267
- size="sm"
268
- />
269
- </label>
270
- </div>
185
+ <div class="grid grid-cols-2 gap-3 sm:grid-cols-4">
186
+ <label class="block">
187
+ <span class="mb-1 block text-[10px] uppercase tracking-wide text-slate-500">
188
+ Max complexity %
189
+ </span>
190
+ <UInput
191
+ v-model.number="drafts[p.id]!.maxComplexity"
192
+ type="number"
193
+ :min="0"
194
+ :max="100"
195
+ size="sm"
196
+ />
197
+ </label>
198
+ <label class="block">
199
+ <span class="mb-1 block text-[10px] uppercase tracking-wide text-slate-500">
200
+ Max risk %
201
+ </span>
202
+ <UInput
203
+ v-model.number="drafts[p.id]!.maxRisk"
204
+ type="number"
205
+ :min="0"
206
+ :max="100"
207
+ size="sm"
208
+ />
209
+ </label>
210
+ <label class="block">
211
+ <span class="mb-1 block text-[10px] uppercase tracking-wide text-slate-500">
212
+ Max impact %
213
+ </span>
214
+ <UInput
215
+ v-model.number="drafts[p.id]!.maxImpact"
216
+ type="number"
217
+ :min="0"
218
+ :max="100"
219
+ size="sm"
220
+ />
221
+ </label>
222
+ <label class="block">
223
+ <span class="mb-1 block text-[10px] uppercase tracking-wide text-slate-500">
224
+ CI-fix attempts
225
+ </span>
226
+ <UInput
227
+ v-model.number="drafts[p.id]!.ciMaxAttempts"
228
+ type="number"
229
+ :min="0"
230
+ :max="50"
231
+ size="sm"
232
+ />
233
+ </label>
234
+ <label class="block">
235
+ <span class="mb-1 block text-[10px] uppercase tracking-wide text-slate-500">
236
+ Requirement iterations
237
+ </span>
238
+ <UInput
239
+ v-model.number="drafts[p.id]!.maxRequirementIterations"
240
+ type="number"
241
+ :min="1"
242
+ :max="20"
243
+ size="sm"
244
+ />
245
+ </label>
246
+ <label class="block">
247
+ <span class="mb-1 block text-[10px] uppercase tracking-wide text-slate-500">
248
+ Auto-pass concerns ≤
249
+ </span>
250
+ <USelect
251
+ v-model="drafts[p.id]!.maxRequirementConcernAllowed"
252
+ :items="CONCERN_LEVELS"
253
+ value-key="value"
254
+ size="sm"
255
+ />
256
+ </label>
257
+ </div>
271
258
 
272
- <div class="mt-3 flex justify-end">
273
- <UButton
274
- color="primary"
275
- variant="soft"
276
- size="xs"
277
- icon="i-lucide-save"
278
- :loading="busy === p.id"
279
- @click="save(p)"
280
- >
281
- Save
282
- </UButton>
283
- </div>
284
- </div>
259
+ <div class="mt-3 flex justify-end">
260
+ <UButton
261
+ color="primary"
262
+ variant="soft"
263
+ size="xs"
264
+ icon="i-lucide-save"
265
+ :loading="busy === p.id"
266
+ @click="save(p)"
267
+ >
268
+ Save
269
+ </UButton>
270
+ </div>
271
+ </div>
285
272
 
286
- <!-- create -->
287
- <div class="rounded-lg border border-dashed border-slate-700 p-3">
288
- <p class="mb-2 text-[11px] font-semibold uppercase tracking-wide text-slate-400">
289
- New preset
290
- </p>
291
- <div class="flex flex-wrap items-end gap-3">
292
- <label class="block min-w-40 flex-1">
293
- <span class="mb-1 block text-[10px] uppercase tracking-wide text-slate-500"
294
- >Name</span
295
- >
296
- <UInput v-model="draft.name" size="sm" placeholder="e.g. Cautious" />
297
- </label>
298
- <label class="block w-20">
299
- <span class="mb-1 block text-[10px] uppercase tracking-wide text-slate-500"
300
- >Cmplx%</span
301
- >
302
- <UInput
303
- v-model.number="draft.maxComplexity"
304
- type="number"
305
- :min="0"
306
- :max="100"
307
- size="sm"
308
- />
309
- </label>
310
- <label class="block w-20">
311
- <span class="mb-1 block text-[10px] uppercase tracking-wide text-slate-500"
312
- >Risk%</span
313
- >
314
- <UInput v-model.number="draft.maxRisk" type="number" :min="0" :max="100" size="sm" />
315
- </label>
316
- <label class="block w-20">
317
- <span class="mb-1 block text-[10px] uppercase tracking-wide text-slate-500"
318
- >Impact%</span
319
- >
320
- <UInput
321
- v-model.number="draft.maxImpact"
322
- type="number"
323
- :min="0"
324
- :max="100"
325
- size="sm"
326
- />
327
- </label>
328
- <label class="block w-20">
329
- <span class="mb-1 block text-[10px] uppercase tracking-wide text-slate-500"
330
- >CI-fix</span
331
- >
332
- <UInput
333
- v-model.number="draft.ciMaxAttempts"
334
- type="number"
335
- :min="0"
336
- :max="50"
337
- size="sm"
338
- />
339
- </label>
340
- <label class="block w-20">
341
- <span class="mb-1 block text-[10px] uppercase tracking-wide text-slate-500"
342
- >Req iter</span
343
- >
344
- <UInput
345
- v-model.number="draft.maxRequirementIterations"
346
- type="number"
347
- :min="1"
348
- :max="20"
349
- size="sm"
350
- />
351
- </label>
352
- <label class="block w-32">
353
- <span class="mb-1 block text-[10px] uppercase tracking-wide text-slate-500"
354
- >Auto-pass ≤</span
355
- >
356
- <USelect
357
- v-model="draft.maxRequirementConcernAllowed"
358
- :items="CONCERN_LEVELS"
359
- value-key="value"
360
- size="sm"
361
- />
362
- </label>
363
- <UButton
364
- color="primary"
365
- size="sm"
366
- icon="i-lucide-plus"
367
- :loading="creating"
368
- :disabled="!draft.name.trim()"
369
- @click="create"
370
- >
371
- Add
372
- </UButton>
373
- </div>
374
- </div>
273
+ <!-- create -->
274
+ <div class="rounded-lg border border-dashed border-slate-700 p-3">
275
+ <p class="mb-2 text-[11px] font-semibold uppercase tracking-wide text-slate-400">
276
+ New preset
277
+ </p>
278
+ <div class="flex flex-wrap items-end gap-3">
279
+ <label class="block min-w-40 flex-1">
280
+ <span class="mb-1 block text-[10px] uppercase tracking-wide text-slate-500">Name</span>
281
+ <UInput v-model="draft.name" size="sm" placeholder="e.g. Cautious" />
282
+ </label>
283
+ <label class="block w-20">
284
+ <span class="mb-1 block text-[10px] uppercase tracking-wide text-slate-500">Cmplx%</span>
285
+ <UInput
286
+ v-model.number="draft.maxComplexity"
287
+ type="number"
288
+ :min="0"
289
+ :max="100"
290
+ size="sm"
291
+ />
292
+ </label>
293
+ <label class="block w-20">
294
+ <span class="mb-1 block text-[10px] uppercase tracking-wide text-slate-500">Risk%</span>
295
+ <UInput v-model.number="draft.maxRisk" type="number" :min="0" :max="100" size="sm" />
296
+ </label>
297
+ <label class="block w-20">
298
+ <span class="mb-1 block text-[10px] uppercase tracking-wide text-slate-500">Impact%</span>
299
+ <UInput v-model.number="draft.maxImpact" type="number" :min="0" :max="100" size="sm" />
300
+ </label>
301
+ <label class="block w-20">
302
+ <span class="mb-1 block text-[10px] uppercase tracking-wide text-slate-500">CI-fix</span>
303
+ <UInput v-model.number="draft.ciMaxAttempts" type="number" :min="0" :max="50" size="sm" />
304
+ </label>
305
+ <label class="block w-20">
306
+ <span class="mb-1 block text-[10px] uppercase tracking-wide text-slate-500"
307
+ >Req iter</span
308
+ >
309
+ <UInput
310
+ v-model.number="draft.maxRequirementIterations"
311
+ type="number"
312
+ :min="1"
313
+ :max="20"
314
+ size="sm"
315
+ />
316
+ </label>
317
+ <label class="block w-32">
318
+ <span class="mb-1 block text-[10px] uppercase tracking-wide text-slate-500"
319
+ >Auto-pass ≤</span
320
+ >
321
+ <USelect
322
+ v-model="draft.maxRequirementConcernAllowed"
323
+ :items="CONCERN_LEVELS"
324
+ value-key="value"
325
+ size="sm"
326
+ />
327
+ </label>
328
+ <UButton
329
+ color="primary"
330
+ size="sm"
331
+ icon="i-lucide-plus"
332
+ :loading="creating"
333
+ :disabled="!draft.name.trim()"
334
+ @click="create"
335
+ >
336
+ Add
337
+ </UButton>
375
338
  </div>
376
- </template>
377
- </UModal>
339
+ </div>
340
+ </div>
378
341
  </template>
@@ -0,0 +1,151 @@
1
+ <script setup lang="ts">
2
+ // The observability connection — the post-release-health gate reads a pluggable
3
+ // observability provider (Datadog today). This panel owns ONLY the per-workspace
4
+ // connection (provider + credentials, write-only). The per-service monitor/SLO mapping
5
+ // lives in the service inspector (ServiceReleaseHealthConfig), so there is no manual
6
+ // block-id entry here. Opened from the Integrations hub.
7
+ import { computed, reactive, ref, watch } from 'vue'
8
+ import type { ObservabilityProviderKind } from '~/types/releaseHealth'
9
+
10
+ const ui = useUiStore()
11
+ const store = useReleaseHealthStore()
12
+ const toast = useToast()
13
+
14
+ const open = computed({
15
+ get: () => ui.observabilityConnectionOpen,
16
+ set: (v: boolean) => (v ? ui.openObservabilityConnection() : ui.closeObservabilityConnection()),
17
+ })
18
+
19
+ // The providers a user can connect. Datadog only today; the picker is ready for more.
20
+ const PROVIDERS: { value: ObservabilityProviderKind; label: string }[] = [
21
+ { value: 'datadog', label: 'Datadog' },
22
+ ]
23
+
24
+ const provider = ref<ObservabilityProviderKind>('datadog')
25
+ const datadog = reactive({ site: 'datadoghq.com', apiKey: '', appKey: '' })
26
+ const busy = ref(false)
27
+
28
+ function notifyError(title: string, e: unknown) {
29
+ toast.add({
30
+ title,
31
+ description: e instanceof Error ? e.message : String(e),
32
+ icon: 'i-lucide-triangle-alert',
33
+ color: 'error',
34
+ })
35
+ }
36
+
37
+ watch(open, async (isOpen) => {
38
+ if (!isOpen) return
39
+ try {
40
+ await store.ensureLoaded()
41
+ if (store.connection.provider) provider.value = store.connection.provider
42
+ const site = store.connection.summary?.site
43
+ if (site) datadog.site = site
44
+ } catch (e) {
45
+ notifyError('Could not load observability settings', e)
46
+ }
47
+ })
48
+
49
+ async function saveConnection() {
50
+ busy.value = true
51
+ try {
52
+ await store.saveConnection({
53
+ provider: provider.value,
54
+ credentials: { site: datadog.site, apiKey: datadog.apiKey, appKey: datadog.appKey },
55
+ })
56
+ datadog.apiKey = ''
57
+ datadog.appKey = ''
58
+ toast.add({ title: 'Observability connected', icon: 'i-lucide-check', color: 'success' })
59
+ } catch (e) {
60
+ notifyError('Could not save the connection', e)
61
+ } finally {
62
+ busy.value = false
63
+ }
64
+ }
65
+
66
+ async function disconnect() {
67
+ busy.value = true
68
+ try {
69
+ await store.removeConnection()
70
+ } catch (e) {
71
+ notifyError('Could not disconnect', e)
72
+ } finally {
73
+ busy.value = false
74
+ }
75
+ }
76
+
77
+ const connectedLabel = computed(() => {
78
+ if (!store.connection.connected) return 'Not connected'
79
+ const site = store.connection.summary?.site
80
+ return site ? `Connected (${site})` : 'Connected'
81
+ })
82
+ </script>
83
+
84
+ <template>
85
+ <UModal v-model:open="open" title="Post-release health" :ui="{ content: 'max-w-lg' }">
86
+ <template #body>
87
+ <div class="space-y-4">
88
+ <p class="text-sm text-slate-400">
89
+ After a release ships, the <code>post-release-health</code> gate watches the configured
90
+ observability monitors/SLOs. On a regression it spawns an on-call agent to investigate (a
91
+ human decides whether to revert). Map which monitors/SLOs a service watches from that
92
+ service's inspector.
93
+ </p>
94
+
95
+ <section class="space-y-3 rounded-lg border border-slate-700 p-3">
96
+ <div class="flex items-center justify-between">
97
+ <h3 class="text-sm font-semibold">Connection</h3>
98
+ <UBadge :color="store.connection.connected ? 'success' : 'neutral'" variant="soft">
99
+ {{ connectedLabel }}
100
+ </UBadge>
101
+ </div>
102
+
103
+ <UFormField label="Provider">
104
+ <USelect v-model="provider" :items="PROVIDERS" value-key="value" class="w-full" />
105
+ </UFormField>
106
+
107
+ <template v-if="provider === 'datadog'">
108
+ <UFormField label="Datadog site">
109
+ <UInput v-model="datadog.site" placeholder="datadoghq.com" class="w-full" />
110
+ </UFormField>
111
+ <UFormField label="API key">
112
+ <UInput
113
+ v-model="datadog.apiKey"
114
+ type="password"
115
+ placeholder="DD-API-KEY"
116
+ class="w-full"
117
+ />
118
+ </UFormField>
119
+ <UFormField label="Application key">
120
+ <UInput
121
+ v-model="datadog.appKey"
122
+ type="password"
123
+ placeholder="DD-APPLICATION-KEY"
124
+ class="w-full"
125
+ />
126
+ </UFormField>
127
+ </template>
128
+
129
+ <div class="flex gap-2">
130
+ <UButton
131
+ :loading="busy"
132
+ :disabled="!datadog.apiKey || !datadog.appKey"
133
+ @click="saveConnection"
134
+ >
135
+ Save connection
136
+ </UButton>
137
+ <UButton
138
+ v-if="store.connection.connected"
139
+ color="error"
140
+ variant="soft"
141
+ :loading="busy"
142
+ @click="disconnect"
143
+ >
144
+ Disconnect
145
+ </UButton>
146
+ </div>
147
+ </section>
148
+ </div>
149
+ </template>
150
+ </UModal>
151
+ </template>