@cat-factory/app 0.270.2 → 0.270.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -2
- package/app/components/board/AddTaskModal.vue +21 -9
- package/app/components/board/RecurringPipelineModal.vue +2 -1
- package/app/components/palettes/PipelinePurposeSelect.vue +3 -2
- package/app/components/panels/inspector/TaskRunSettings.vue +13 -4
- package/app/stores/pipelines.ts +4 -4
- package/app/utils/pipeline.spec.ts +117 -4
- package/app/utils/pipeline.ts +32 -5
- package/app/utils/pipelineLibrary.spec.ts +16 -0
- package/i18n/locales/de.json +2 -1
- package/i18n/locales/en.json +2 -1
- package/i18n/locales/es.json +2 -1
- package/i18n/locales/fr.json +2 -1
- package/i18n/locales/he.json +2 -1
- package/i18n/locales/it.json +2 -1
- package/i18n/locales/ja.json +2 -1
- package/i18n/locales/pl.json +2 -1
- package/i18n/locales/tr.json +2 -1
- package/i18n/locales/uk.json +2 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -348,8 +348,12 @@ picking what each of them runs on are halves of the same job.
|
|
|
348
348
|
The builder's palette narrows on two axes, and both controls sit on one row above the catalog
|
|
349
349
|
(`PipelinePurposeSelect` above `AgentTierSelect`), each with its own "n hidden" hint so neither
|
|
350
350
|
narrowing reads as an empty catalog. The tier says how deep to look; the **purpose** says what
|
|
351
|
-
the pipeline is for (`build` / `document` / `review` / `research` / `planning`), and the
|
|
352
|
-
drops the categories that purpose has no use for.
|
|
351
|
+
the pipeline is for (`build` / `bugfix` / `document` / `review` / `research` / `planning`), and the
|
|
352
|
+
palette drops the categories that purpose has no use for. `bugfix` is the one pair that shares a
|
|
353
|
+
row with another member: it ships code exactly as `build` does, and differs only in being offered
|
|
354
|
+
to a `bug` task and withheld from a `feature` one, because a preset that investigates a defect
|
|
355
|
+
report and writes a failing reproduction test has neither input on a feature. It reaches past the
|
|
356
|
+
palette: the saved-pipeline
|
|
353
357
|
library in the builder's third column lists the pipelines built for the purpose being edited, so
|
|
354
358
|
one dial narrows both ends of the slideover. The purpose is not a view preference either: it is
|
|
355
359
|
saved on the pipeline and also decides which task pickers offer it, which is why the control
|
|
@@ -380,13 +380,14 @@ const selectedModelPresetLabel = computed(() => {
|
|
|
380
380
|
// still browsable.
|
|
381
381
|
const fragmentPool = computed(() => fragments.forBlockType(frame.value?.type ?? 'service'))
|
|
382
382
|
|
|
383
|
-
// Hide UI-testing pipelines
|
|
384
|
-
//
|
|
385
|
-
// hide `'recurring'`-only pipelines (a one-off task start of one is
|
|
386
|
-
// pipeline whose purpose doesn't match the chosen task type (a doc
|
|
387
|
-
// reviews a PR,
|
|
388
|
-
//
|
|
389
|
-
//
|
|
383
|
+
// Hide UI-testing pipelines when the target frame has no UI for them to reach — a step scoped to
|
|
384
|
+
// a frontend service excuses itself, so this only drops an UNCONDITIONAL one (see utils/pipeline
|
|
385
|
+
// + the backend gate). Also hide `'recurring'`-only pipelines (a one-off task start of one is
|
|
386
|
+
// refused at run start) and every pipeline whose purpose doesn't match the chosen task type (a doc
|
|
387
|
+
// task authors a doc, a review task reviews a PR, a `bug` task ships code and may reach for a
|
|
388
|
+
// bugfix preset, and a `feature` gets that set minus the bugfix ones, which have no defect report
|
|
389
|
+
// to investigate). `blockLevel: 'task'` is passed literally because this modal only ever creates a
|
|
390
|
+
// task leaf, which also drops the three planning presets the backend would refuse.
|
|
390
391
|
// Re-filters as the chosen task type changes.
|
|
391
392
|
const selectablePipelines = computed(() =>
|
|
392
393
|
pipelines.pipelines.filter((p) =>
|
|
@@ -416,7 +417,15 @@ const DEFAULT_PIPELINE_FOR_TYPE: Partial<Record<TaskTypeChoice, string>> = {
|
|
|
416
417
|
* deliberately does not name), the build rung this interface mode defaults to. Basic mode gets the
|
|
417
418
|
* fixed Standard build, advanced the Adaptive one; `defaultBuildPipelineId` owns that rule so the
|
|
418
419
|
* create form and the task card's plain "Start" cannot disagree about it. Empty when the resolved
|
|
419
|
-
* preset is not in this workspace's library (an older seed, or a retired rung)
|
|
420
|
+
* preset is not in this workspace's library (an older seed, or a retired rung) or when the picker
|
|
421
|
+
* would not OFFER it for `type`.
|
|
422
|
+
*
|
|
423
|
+
* That second check is why it re-runs the picker's own predicate rather than scanning the whole
|
|
424
|
+
* library: a workspace whose declared interactive default is a bugfix preset would otherwise open a
|
|
425
|
+
* `feature` form on a selection with no matching row, and create the task pinned to a pipeline the
|
|
426
|
+
* same screen says is not allowed for its type. Parameterised by `type` rather than reading
|
|
427
|
+
* `selectablePipelines`, so the answer cannot depend on whether the watcher fires before or after
|
|
428
|
+
* the ref it filters on has settled.
|
|
420
429
|
*
|
|
421
430
|
* ONE definition, read by both the type watcher and the open-reset. They used to compute it
|
|
422
431
|
* separately, the reset consulting `DEFAULT_PIPELINE_FOR_TYPE` alone and falling to `''` for every
|
|
@@ -433,7 +442,10 @@ function defaultPipelineIdFor(type: TaskTypeChoice): string {
|
|
|
433
442
|
// and the task card's plain Start still cannot disagree (see `declaredDefaultId`).
|
|
434
443
|
pipelines.declaredDefaultId('interactive') ??
|
|
435
444
|
defaultBuildPipelineId(uiMode.isAdvanced)
|
|
436
|
-
|
|
445
|
+
const resolved = pipelines.pipelines.find((p) => p.id === preset)
|
|
446
|
+
const offered =
|
|
447
|
+
!!resolved && pipelineAllowedForManualStart(resolved, frame.value, board.blocks, type, 'task')
|
|
448
|
+
return offered ? preset : ''
|
|
437
449
|
}
|
|
438
450
|
|
|
439
451
|
watch(taskType, (next) => {
|
|
@@ -131,7 +131,8 @@ function defaultRecurrence(): Recurrence {
|
|
|
131
131
|
}
|
|
132
132
|
}
|
|
133
133
|
|
|
134
|
-
// Hide UI-testing
|
|
134
|
+
// Hide a pipeline whose UI-testing step would REACH a frame with no UI (one scoped to a frontend
|
|
135
|
+
// service excuses itself) — it would be refused at run start.
|
|
135
136
|
// Also hide `'one-off'`-only pipelines: attaching one to a schedule is refused server-side.
|
|
136
137
|
const selectablePipelines = computed(() =>
|
|
137
138
|
pipelines.pipelines.filter((p) => pipelineAllowedForSchedule(p, frame.value, board.blocks)),
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
// The pipeline-PURPOSE control: what the pipeline being built exists to do (build,
|
|
3
|
-
// review, research, plan). It sits beside `AgentTierSelect` at the top of the agent palette
|
|
2
|
+
// The pipeline-PURPOSE control: what the pipeline being built exists to do (build, fix a bug,
|
|
3
|
+
// document, review, research, plan). It sits beside `AgentTierSelect` at the top of the agent palette
|
|
4
4
|
// because the two are the same kind of dial (each narrows the catalog below to what is worth
|
|
5
5
|
// offering), and a filter the user cannot see is a catalog that looks incomplete for no reason.
|
|
6
6
|
//
|
|
@@ -25,6 +25,7 @@ const emit = defineEmits<{ (e: 'update:purpose', purpose: PipelinePurpose): void
|
|
|
25
25
|
// typed-message-keys check covers them, the same shape `AgentTierSelect` uses for its tiers.
|
|
26
26
|
const PURPOSE_LABELS = computed<Record<PipelinePurpose, string>>(() => ({
|
|
27
27
|
build: t('pipeline.builder.purposeOption.build'),
|
|
28
|
+
bugfix: t('pipeline.builder.purposeOption.bugfix'),
|
|
28
29
|
document: t('pipeline.builder.purposeOption.document'),
|
|
29
30
|
review: t('pipeline.builder.purposeOption.review'),
|
|
30
31
|
research: t('pipeline.builder.purposeOption.research'),
|
|
@@ -148,12 +148,21 @@ const selectedPipeline = computed(() =>
|
|
|
148
148
|
// drops the planning presets the engine would refuse. The task-type narrowing already excludes them
|
|
149
149
|
// for `feature`/`bug`, but NOT for a `spike` / `ralph` / deployment-custom type — and a planning
|
|
150
150
|
// preset settable as a task's DEFAULT pipeline is a 409 on every later Start.
|
|
151
|
+
// The pipeline this task is ALREADY pinned to is kept in the list even when the narrowing would
|
|
152
|
+
// drop it, the same rule `showOverrideField` states for a hidden override an entity already
|
|
153
|
+
// carries: every one of these gates has been tightened at least once, and a task pinned under the
|
|
154
|
+
// looser one is a legal pre-existing state (a `feature` on a bugfix preset, until the purpose
|
|
155
|
+
// classifier split). Without it the badge below names a pipeline the dropdown does not contain,
|
|
156
|
+
// and the first change to anything else is a one-way door out of a selection the panel is still
|
|
157
|
+
// showing. It is only ever the CURRENT value, so nothing new can be pinned through it.
|
|
151
158
|
const taskFrame = computed(() => board.serviceOf(props.block))
|
|
152
|
-
const selectablePipelines = computed(() =>
|
|
153
|
-
pipelines.pipelines.filter((p) =>
|
|
159
|
+
const selectablePipelines = computed(() => {
|
|
160
|
+
const offered = pipelines.pipelines.filter((p) =>
|
|
154
161
|
pipelineAllowedForManualStart(p, taskFrame.value, board.blocks, props.block.taskType, 'task'),
|
|
155
|
-
)
|
|
156
|
-
|
|
162
|
+
)
|
|
163
|
+
const pinned = selectedPipeline.value
|
|
164
|
+
return pinned && !offered.some((p) => p.id === pinned.id) ? [pinned, ...offered] : offered
|
|
165
|
+
})
|
|
157
166
|
function setPipeline(id: string) {
|
|
158
167
|
board.updateBlock(props.block.id, { pipelineId: id })
|
|
159
168
|
}
|
package/app/stores/pipelines.ts
CHANGED
|
@@ -75,10 +75,10 @@ export const usePipelinesStore = defineStore('pipelines', () => {
|
|
|
75
75
|
/** Organizational labels for the pipeline being assembled/edited. */
|
|
76
76
|
const draftLabels = ref<string[]>([])
|
|
77
77
|
/**
|
|
78
|
-
* The use-case classifier of the pipeline being assembled/edited (`build` / `
|
|
79
|
-
* `review` / `research` / `planning`). Drives which task pickers offer the saved
|
|
80
|
-
* agent kinds the builder palette shows (a
|
|
81
|
-
* kinds) and which saved pipelines the library lists.
|
|
78
|
+
* The use-case classifier of the pipeline being assembled/edited (`build` / `bugfix` /
|
|
79
|
+
* `document` / `review` / `research` / `planning`). Drives which task pickers offer the saved
|
|
80
|
+
* pipeline, which agent kinds the builder palette shows (a purpose that ships no code hides the
|
|
81
|
+
* Implementation/Testing kinds) and which saved pipelines the library lists.
|
|
82
82
|
*
|
|
83
83
|
* Never null: `Pipeline.purpose` is mandatory, so a draft is classified from the moment it
|
|
84
84
|
* exists. `build` is the default because it is what an unclassified pipeline has always behaved
|
|
@@ -6,13 +6,17 @@ import {
|
|
|
6
6
|
PIPELINE_PURPOSES,
|
|
7
7
|
pipelineAllowedForBlockLevel,
|
|
8
8
|
pipelineAllowedForTaskType,
|
|
9
|
+
pipelineMatchesPurpose,
|
|
10
|
+
pipelineRunsVisualStep,
|
|
9
11
|
purposeAllowsAgentCategory,
|
|
10
12
|
purposeSuggestsAgentCategory,
|
|
11
13
|
purposeSuggestsAgentKind,
|
|
14
|
+
resolveRunServiceScope,
|
|
12
15
|
} from '@cat-factory/contracts'
|
|
13
16
|
import type { PipelinePurpose } from '@cat-factory/contracts'
|
|
14
17
|
import type { Block, Pipeline } from '~/types/domain'
|
|
15
18
|
import {
|
|
19
|
+
pipelineAllowedForFrame,
|
|
16
20
|
pipelineAllowedForManualStart,
|
|
17
21
|
pipelineAllowedForSchedule,
|
|
18
22
|
pipelineConditionalCount,
|
|
@@ -136,6 +140,22 @@ describe('pipelineAllowedForTaskType', () => {
|
|
|
136
140
|
}
|
|
137
141
|
})
|
|
138
142
|
|
|
143
|
+
it('offers a bugfix preset to a bug task and withholds it from a feature', () => {
|
|
144
|
+
// "Triage & fix bug" investigates a defect REPORT, triages it with a person and writes a
|
|
145
|
+
// failing reproduction test before anything is fixed. A feature has no report to investigate
|
|
146
|
+
// and nothing red to turn green, so the front half of the run has no input at all. The bug
|
|
147
|
+
// task still gets the whole build ladder beside it — this narrows one preset, not the type.
|
|
148
|
+
const bugfix = pipeline({ purpose: 'bugfix' })
|
|
149
|
+
expect(pipelineAllowedForTaskType(bugfix, 'bug')).toBe(true)
|
|
150
|
+
expect(pipelineAllowedForTaskType(bugfix, 'feature')).toBe(false)
|
|
151
|
+
expect(pipelineAllowedForTaskType(pipeline({ purpose: 'build' }), 'bug')).toBe(true)
|
|
152
|
+
// And it stays out of the task types that demand their own explicit member.
|
|
153
|
+
expect(pipelineAllowedForTaskType(bugfix, 'document')).toBe(false)
|
|
154
|
+
expect(pipelineAllowedForTaskType(bugfix, 'review')).toBe(false)
|
|
155
|
+
// An un-narrowed type (spike, ralph, a deployment's own) is unrestricted as ever.
|
|
156
|
+
expect(pipelineAllowedForTaskType(bugfix, 'spike')).toBe(true)
|
|
157
|
+
})
|
|
158
|
+
|
|
139
159
|
it('keeps a pipeline whose classifier this build cannot name on a feature / bug task', () => {
|
|
140
160
|
// The one place this narrowing runs opposite to the document/review one, and it has to. The
|
|
141
161
|
// value is persisted, so a deployment's own classifier (or one retired since the row was
|
|
@@ -189,8 +209,10 @@ describe('pipelineAllowedForBlockLevel (initiative binding)', () => {
|
|
|
189
209
|
})
|
|
190
210
|
|
|
191
211
|
describe('purposeAllowsAgentCategory (builder save gate)', () => {
|
|
192
|
-
it('a
|
|
193
|
-
|
|
212
|
+
it('a code-shipping pipeline, and one whose classifier this build cannot name, may use every category', () => {
|
|
213
|
+
// `bugfix` rides with `build` here: the two differ only in the task type they are OFFERED to,
|
|
214
|
+
// and a bug fix designs, implements, tests and merges like any other change.
|
|
215
|
+
for (const purpose of ['build', 'bugfix', UNKNOWN_PURPOSE] as const) {
|
|
194
216
|
for (const cat of AGENT_CATEGORIES) {
|
|
195
217
|
expect(purposeAllowsAgentCategory(purpose, cat)).toBe(true)
|
|
196
218
|
}
|
|
@@ -211,8 +233,8 @@ describe('purposeAllowsAgentCategory (builder save gate)', () => {
|
|
|
211
233
|
})
|
|
212
234
|
|
|
213
235
|
describe('purposeSuggestsAgentCategory (builder palette filter)', () => {
|
|
214
|
-
it('offers the whole catalog to a
|
|
215
|
-
for (const purpose of ['build', UNKNOWN_PURPOSE] as const) {
|
|
236
|
+
it('offers the whole catalog to a code-shipping pipeline, and to one it cannot name', () => {
|
|
237
|
+
for (const purpose of ['build', 'bugfix', UNKNOWN_PURPOSE] as const) {
|
|
216
238
|
for (const cat of AGENT_CATEGORIES) {
|
|
217
239
|
expect(purposeSuggestsAgentCategory(purpose, cat)).toBe(true)
|
|
218
240
|
}
|
|
@@ -273,6 +295,23 @@ describe('purposeSuggestsAgentKind (what the palette actually filters on)', () =
|
|
|
273
295
|
expect(purposeSuggestsAgentCategory('review', 'docs')).toBe(true)
|
|
274
296
|
})
|
|
275
297
|
|
|
298
|
+
it('reads a `build` declaration as covering `bugfix`, but not the other way round', () => {
|
|
299
|
+
// The two purposes are the same WORK, split only so the pickers can withhold a defect-report
|
|
300
|
+
// preset from a feature task. Every kind declaring `build` predates the split, so without
|
|
301
|
+
// this the bugfix palette would open nearly empty — the Bug Investigator included.
|
|
302
|
+
const builder = { category: 'build', purposes: ['build'] } as const
|
|
303
|
+
expect(purposeSuggestsAgentKind('bugfix', builder)).toBe(true)
|
|
304
|
+
expect(purposeSuggestsAgentKind('document', builder)).toBe(false)
|
|
305
|
+
// One-way: naming only `bugfix` claims the defect-report context, not builds in general.
|
|
306
|
+
const bugOnly = { category: 'build', purposes: ['bugfix'] } as const
|
|
307
|
+
expect(purposeSuggestsAgentKind('bugfix', bugOnly)).toBe(true)
|
|
308
|
+
expect(purposeSuggestsAgentKind('build', bugOnly)).toBe(false)
|
|
309
|
+
// ONE relation, not one per surface: the saved-pipeline library reads it through the same
|
|
310
|
+
// helper, so a draft cannot find the build ladder in its palette and not in its library.
|
|
311
|
+
expect(pipelineMatchesPurpose({ purpose: 'build' } as Pipeline, 'bugfix')).toBe(true)
|
|
312
|
+
expect(pipelineMatchesPurpose({ purpose: 'bugfix' } as Pipeline, 'build')).toBe(false)
|
|
313
|
+
})
|
|
314
|
+
|
|
276
315
|
it('is a declaration, not an exemption from the category', () => {
|
|
277
316
|
// A kind cannot buy its way back into a purpose its section is not offered to: `docs` is gone
|
|
278
317
|
// for `planning`, and the palette drops the kind whether or not it named `planning` itself.
|
|
@@ -341,6 +380,80 @@ describe('a purpose or category this build does not recognise', () => {
|
|
|
341
380
|
})
|
|
342
381
|
})
|
|
343
382
|
|
|
383
|
+
describe('pipelineAllowedForFrame (the visual gate)', () => {
|
|
384
|
+
const serviceFrame = { id: 'blk_svc', level: 'frame', type: 'service' } as Block
|
|
385
|
+
const frontendFrame = { id: 'blk_fe', level: 'frame', type: 'frontend' } as Block
|
|
386
|
+
|
|
387
|
+
// Every build rung ships the CONDITIONAL tester pair: the browser pass declares the frontend
|
|
388
|
+
// scope, the API pass the backend one, so one preset covers both kinds of service.
|
|
389
|
+
const buildRung = pipeline({
|
|
390
|
+
purpose: 'build',
|
|
391
|
+
agentKinds: ['coder', 'tester-api', 'tester-ui', 'merger'],
|
|
392
|
+
stepOptions: [
|
|
393
|
+
null,
|
|
394
|
+
{ condition: { serviceScope: 'backend' } },
|
|
395
|
+
{ condition: { serviceScope: 'frontend' } },
|
|
396
|
+
null,
|
|
397
|
+
],
|
|
398
|
+
})
|
|
399
|
+
|
|
400
|
+
it('offers a build rung on a plain backend service — its UI pass is scoped out', () => {
|
|
401
|
+
// The regression: reading "does the pipeline LIST a visual step" hid Standard / Simple /
|
|
402
|
+
// Adaptive / Complex build from the pickers on every non-frontend service, while the engine
|
|
403
|
+
// would have started any of them (it drops the condition-excluded steps before its own gate).
|
|
404
|
+
expect(pipelineAllowedForFrame(buildRung, serviceFrame, [serviceFrame])).toBe(true)
|
|
405
|
+
expect(pipelineAllowedForManualStart(buildRung, serviceFrame, [serviceFrame], 'feature')).toBe(
|
|
406
|
+
true,
|
|
407
|
+
)
|
|
408
|
+
})
|
|
409
|
+
|
|
410
|
+
it('offers it on a frontend frame too, where the UI pass is exactly what runs', () => {
|
|
411
|
+
// Stated as the premise, not just the answer: on this frame the condition ADMITS the browser
|
|
412
|
+
// pass, so the gate is passing on the frame half rather than on the step being scoped out.
|
|
413
|
+
// Without it the case reads identically against the pre-fix implementation, which reached the
|
|
414
|
+
// same `true` down the other branch and asserted nothing about the change.
|
|
415
|
+
expect(pipelineRunsVisualStep(buildRung, resolveRunServiceScope([frontendFrame]))).toBe(true)
|
|
416
|
+
expect(pipelineAllowedForFrame(buildRung, frontendFrame, [frontendFrame])).toBe(true)
|
|
417
|
+
})
|
|
418
|
+
|
|
419
|
+
it('offers everything on a service frame a FRONTEND binds as its backend', () => {
|
|
420
|
+
// The other half of `frameAllowsVisualPipeline`, and the branch with the widest reach: the
|
|
421
|
+
// linked frontend is the UI a change to this service is validated through, so the frame has a
|
|
422
|
+
// UI and even an unconditional visual pipeline may run on it.
|
|
423
|
+
const boundService = { id: 'blk_api', level: 'frame', type: 'service' } as Block
|
|
424
|
+
const binder = {
|
|
425
|
+
id: 'blk_fe',
|
|
426
|
+
level: 'frame',
|
|
427
|
+
type: 'frontend',
|
|
428
|
+
frontendConfig: {
|
|
429
|
+
backendBindings: [
|
|
430
|
+
{ envVar: 'API_URL', source: { kind: 'service', serviceBlockId: 'blk_api' } },
|
|
431
|
+
],
|
|
432
|
+
},
|
|
433
|
+
} as Block
|
|
434
|
+
const blocks = [boundService, binder]
|
|
435
|
+
const visual = pipeline({ purpose: 'build', agentKinds: ['coder', 'visual-confirmation'] })
|
|
436
|
+
expect(pipelineAllowedForFrame(buildRung, boundService, blocks)).toBe(true)
|
|
437
|
+
expect(pipelineAllowedForFrame(visual, boundService, blocks)).toBe(true)
|
|
438
|
+
// ...and it is the BINDING that does it, not the mere presence of a frontend on the board.
|
|
439
|
+
expect(pipelineAllowedForFrame(visual, boundService, [boundService, frontendFrame])).toBe(false)
|
|
440
|
+
})
|
|
441
|
+
|
|
442
|
+
it('still hides a pipeline whose visual step is UNCONDITIONAL on a frame with no UI', () => {
|
|
443
|
+
// The gate the conditional pair did not repeal: a `visual-confirmation` step with nothing to
|
|
444
|
+
// scope it out has no app to show a person, and the engine answers a 409.
|
|
445
|
+
const visual = pipeline({ purpose: 'build', agentKinds: ['coder', 'visual-confirmation'] })
|
|
446
|
+
expect(pipelineAllowedForFrame(visual, serviceFrame, [serviceFrame])).toBe(false)
|
|
447
|
+
expect(pipelineAllowedForFrame(visual, frontendFrame, [frontendFrame])).toBe(true)
|
|
448
|
+
})
|
|
449
|
+
|
|
450
|
+
it('hides a conditional visual pipeline when the frame cannot be resolved', () => {
|
|
451
|
+
// No frame ⇒ no scope to judge the condition against, so the step counts and the frame half
|
|
452
|
+
// refuses. The fail-safe direction, and the same answer the engine gives.
|
|
453
|
+
expect(pipelineAllowedForFrame(buildRung, undefined, [])).toBe(false)
|
|
454
|
+
})
|
|
455
|
+
})
|
|
456
|
+
|
|
344
457
|
describe('pipelineAllowedForManualStart composes the task-type gate', () => {
|
|
345
458
|
const noFrame = undefined
|
|
346
459
|
const blocks: Block[] = []
|
package/app/utils/pipeline.ts
CHANGED
|
@@ -3,6 +3,8 @@ import {
|
|
|
3
3
|
pipelineAllowedForBlockLevel,
|
|
4
4
|
pipelineAllowedForTaskType,
|
|
5
5
|
pipelineHasVisualStep,
|
|
6
|
+
pipelineRunsVisualStep,
|
|
7
|
+
resolveRunServiceScope,
|
|
6
8
|
} from '@cat-factory/contracts'
|
|
7
9
|
import type { AgentKind, Block, BlockLevel, Pipeline } from '~/types/domain'
|
|
8
10
|
|
|
@@ -102,16 +104,40 @@ export { pipelineAllowedForBlockLevel, pipelineAllowedForTaskType }
|
|
|
102
104
|
// the gate.
|
|
103
105
|
|
|
104
106
|
/**
|
|
105
|
-
* Whether `pipeline` may run on a task under `frame`.
|
|
106
|
-
*
|
|
107
|
-
* the board's block list, used to
|
|
107
|
+
* Whether `pipeline` may run on a task under `frame`. Allowed when the frame has a UI (see
|
|
108
|
+
* {@link frameAllowsVisualPipeline}), and otherwise only when no visual step would REACH a
|
|
109
|
+
* dispatch there ({@link pipelineRunsVisualStep}). `blocks` is the board's block list, used to
|
|
110
|
+
* find frontend→service links.
|
|
111
|
+
*
|
|
112
|
+
* The two halves mirror run admission, which filters the chain through the run conditions and
|
|
113
|
+
* then gates the survivors on the frame. Asking only "does it list a visual step" hid every
|
|
114
|
+
* build rung from every picker on every non-frontend service the moment the ladder adopted the
|
|
115
|
+
* conditional tester pair: each rung lists a `tester-ui` scoped to `frontend`, which a backend
|
|
116
|
+
* task skips.
|
|
117
|
+
*
|
|
118
|
+
* The coarse {@link pipelineHasVisualStep} runs FIRST and is the only work an ordinary pipeline
|
|
119
|
+
* costs: this is called once per row of every pipeline picker, recomputed on each task-type change
|
|
120
|
+
* and each live board push, and the other two halves each walk the whole block list or allocate a
|
|
121
|
+
* scope. A pipeline listing no visual step at all can reach neither answer, so nothing is decided
|
|
122
|
+
* before it that it could decide differently.
|
|
123
|
+
*
|
|
124
|
+
* The scope is derived from the FRAME alone, where the engine also folds in the task's involved
|
|
125
|
+
* services. That cannot disagree here: an involved service is always a non-frontend (only a
|
|
126
|
+
* `service` frame survives `validInvolvedServiceFrames`), so it can only ever re-confirm the
|
|
127
|
+
* `backend` half this frame already sets — and on a frontend frame the gate passes on the frame
|
|
128
|
+
* itself. `runServiceScope.test.ts` pins that filter as the invariant this omission rests on, so
|
|
129
|
+
* widening it fails there rather than here, where the symptom would be a rung this offers and the
|
|
130
|
+
* run-start gate then refuses. An unresolved frame yields an empty scope, which admits every
|
|
131
|
+
* condition and is then refused by the frame half, exactly as the engine refuses it.
|
|
108
132
|
*/
|
|
109
133
|
export function pipelineAllowedForFrame(
|
|
110
134
|
pipeline: Pipeline,
|
|
111
135
|
frame: Block | undefined,
|
|
112
136
|
blocks: readonly Block[],
|
|
113
137
|
): boolean {
|
|
114
|
-
|
|
138
|
+
if (!pipelineHasVisualStep(pipeline)) return true
|
|
139
|
+
if (frameAllowsVisualPipeline(frame, blocks)) return true
|
|
140
|
+
return !pipelineRunsVisualStep(pipeline, resolveRunServiceScope(frame ? [frame] : []))
|
|
115
141
|
}
|
|
116
142
|
|
|
117
143
|
// Launch-availability filters, the surface counterpart to the backend's start-origin gate (a
|
|
@@ -126,7 +152,8 @@ export function pipelineAllowedForFrame(
|
|
|
126
152
|
* - `'recurring'`-only pipelines the backend would refuse;
|
|
127
153
|
* - visual pipelines on a frame with no UI;
|
|
128
154
|
* - pipelines whose `purpose` doesn't fit the given `taskType` (a `document` task offers only
|
|
129
|
-
* document pipelines; a `
|
|
155
|
+
* document pipelines; a `bug` task build + bugfix + research ones, and a `feature` the same
|
|
156
|
+
* minus the bugfix presets, which have no defect report to work from);
|
|
130
157
|
* - pipelines whose `purpose` doesn't fit the given `blockLevel` (planning pipelines run only on
|
|
131
158
|
* an initiative block, and an initiative block runs only those).
|
|
132
159
|
*
|
|
@@ -82,6 +82,22 @@ describe('narrowPipelineLibrary', () => {
|
|
|
82
82
|
expect(shown.archivedInScope).toBe(1)
|
|
83
83
|
})
|
|
84
84
|
|
|
85
|
+
it('lists a build pipeline in a BUGFIX library, but not the reverse', () => {
|
|
86
|
+
// The one relation `classifierSuits` admits, read here through the same helper the palette
|
|
87
|
+
// reads: a bugfix draft is doing build work, so the whole ladder belongs in front of it, and
|
|
88
|
+
// exact matching would open that library holding the two bugfix presets alone, the same
|
|
89
|
+
// near-empty catalog the palette rule exists to prevent. A build draft is NOT doing defect
|
|
90
|
+
// work, so the bugfix rows stay behind the dial, counted and one click away.
|
|
91
|
+
const lib = [...LIBRARY, row('bugfix-a', 'bugfix')]
|
|
92
|
+
expect(narrowPipelineLibrary(lib, { purpose: 'bugfix' }).offered.map((p) => p.id)).toEqual([
|
|
93
|
+
'build-a',
|
|
94
|
+
'bugfix-a',
|
|
95
|
+
])
|
|
96
|
+
const atBuild = narrowPipelineLibrary(lib, { purpose: 'build' })
|
|
97
|
+
expect(atBuild.offered.map((p) => p.id)).toEqual(['build-a'])
|
|
98
|
+
expect(atBuild.hiddenByPurpose).toBe(4)
|
|
99
|
+
})
|
|
100
|
+
|
|
85
101
|
it('narrows by neither purpose this build cannot name', () => {
|
|
86
102
|
// Same disposition as the palette's: an unrecognised value is one this build has nothing to
|
|
87
103
|
// narrow BY and nothing to narrow it AGAINST, never a reason to guess a current member. A
|
package/i18n/locales/de.json
CHANGED
|
@@ -4391,6 +4391,7 @@
|
|
|
4391
4391
|
"purposeUnrecognized": "Nicht erkannt ({purpose})",
|
|
4392
4392
|
"purposeOption": {
|
|
4393
4393
|
"build": "Entwicklung",
|
|
4394
|
+
"bugfix": "Fehlerbehebung",
|
|
4394
4395
|
"document": "Dokumentation",
|
|
4395
4396
|
"review": "Review",
|
|
4396
4397
|
"research": "Recherche",
|
|
@@ -4459,7 +4460,7 @@
|
|
|
4459
4460
|
"skillNoneAvailable": "Keine Skills verfügbar. Verknüpfe eine Skill-Quelle in den Kontoeinstellungen.",
|
|
4460
4461
|
"skillMissing": "Dieser Skill ist nicht mehr im Katalog; wähle einen anderen.",
|
|
4461
4462
|
"skillNeedsPick": "Ein Skill-Schritt braucht einen ausgewählten Skill, bevor du speichern kannst.",
|
|
4462
|
-
"purposeStepsConflict": "Dieser Zweck schreibt keinen Code und führt keine Tests aus, doch die Pipeline enthält noch Implementierungs- oder Testschritte. Entfernen Sie diese oder
|
|
4463
|
+
"purposeStepsConflict": "Dieser Zweck schreibt keinen Code und führt keine Tests aus, doch die Pipeline enthält noch Implementierungs- oder Testschritte. Entfernen Sie diese oder wählen Sie einen Zweck, der Code liefert (Entwicklung oder Fehlerbehebung).",
|
|
4463
4464
|
"promptEditTooltip": "Systemprompt dieses Agenten bearbeiten",
|
|
4464
4465
|
"promptEditedTooltip": "Der Systemprompt dieses Agenten ist für diesen Arbeitsbereich bearbeitet",
|
|
4465
4466
|
"consensusGroups": "Konsensgruppen",
|
package/i18n/locales/en.json
CHANGED
|
@@ -4998,6 +4998,7 @@
|
|
|
4998
4998
|
},
|
|
4999
4999
|
"purposeOption": {
|
|
5000
5000
|
"build": "Build",
|
|
5001
|
+
"bugfix": "Bug fix",
|
|
5001
5002
|
"document": "Documentation",
|
|
5002
5003
|
"review": "Review",
|
|
5003
5004
|
"research": "Research",
|
|
@@ -5069,7 +5070,7 @@
|
|
|
5069
5070
|
"skillNoneAvailable": "No skills available. Link a skill source in account settings.",
|
|
5070
5071
|
"skillMissing": "This skill is no longer in the catalog; pick another.",
|
|
5071
5072
|
"skillNeedsPick": "A Skill step needs a skill selected before you can save.",
|
|
5072
|
-
"purposeStepsConflict": "This purpose writes no code and runs no tests, but the pipeline still has implementation or testing steps. Remove them or switch
|
|
5073
|
+
"purposeStepsConflict": "This purpose writes no code and runs no tests, but the pipeline still has implementation or testing steps. Remove them, or switch to a purpose that ships code (Build or Bug fix).",
|
|
5073
5074
|
"promptEditTooltip": "Edit this agent's system prompt",
|
|
5074
5075
|
"promptEditedTooltip": "This agent's system prompt is edited for this workspace",
|
|
5075
5076
|
"consensusGroups": "Consensus groups",
|
package/i18n/locales/es.json
CHANGED
|
@@ -4834,6 +4834,7 @@
|
|
|
4834
4834
|
"purposeUnrecognized": "No reconocido ({purpose})",
|
|
4835
4835
|
"purposeOption": {
|
|
4836
4836
|
"build": "Desarrollo",
|
|
4837
|
+
"bugfix": "Corrección de errores",
|
|
4837
4838
|
"document": "Documentación",
|
|
4838
4839
|
"review": "Revisión",
|
|
4839
4840
|
"research": "Investigación",
|
|
@@ -4902,7 +4903,7 @@
|
|
|
4902
4903
|
"skillNoneAvailable": "No hay habilidades disponibles. Vincula una fuente de habilidades en la configuración de la cuenta.",
|
|
4903
4904
|
"skillMissing": "Esta habilidad ya no está en el catálogo; elige otra.",
|
|
4904
4905
|
"skillNeedsPick": "Un paso Skill necesita una habilidad seleccionada antes de poder guardar.",
|
|
4905
|
-
"purposeStepsConflict": "Este propósito no escribe código ni ejecuta pruebas, pero la canalización todavía tiene pasos de implementación o de prueba. Quítalos o cambia
|
|
4906
|
+
"purposeStepsConflict": "Este propósito no escribe código ni ejecuta pruebas, pero la canalización todavía tiene pasos de implementación o de prueba. Quítalos o cambia a un propósito que entregue código (Desarrollo o Corrección de errores).",
|
|
4906
4907
|
"promptEditTooltip": "Editar el prompt del sistema de este agente",
|
|
4907
4908
|
"promptEditedTooltip": "El prompt del sistema de este agente está editado para este espacio de trabajo",
|
|
4908
4909
|
"consensusGroups": "Grupos de consenso",
|
package/i18n/locales/fr.json
CHANGED
|
@@ -4834,6 +4834,7 @@
|
|
|
4834
4834
|
"purposeUnrecognized": "Non reconnu ({purpose})",
|
|
4835
4835
|
"purposeOption": {
|
|
4836
4836
|
"build": "Développement",
|
|
4837
|
+
"bugfix": "Correction de bug",
|
|
4837
4838
|
"document": "Documentation",
|
|
4838
4839
|
"review": "Revue",
|
|
4839
4840
|
"research": "Recherche",
|
|
@@ -4902,7 +4903,7 @@
|
|
|
4902
4903
|
"skillNoneAvailable": "Aucune compétence disponible. Reliez une source de compétences dans les paramètres du compte.",
|
|
4903
4904
|
"skillMissing": "Cette compétence n'est plus dans le catalogue ; choisissez-en une autre.",
|
|
4904
4905
|
"skillNeedsPick": "Une étape Skill a besoin d'une compétence sélectionnée avant de pouvoir enregistrer.",
|
|
4905
|
-
"purposeStepsConflict": "Cet objectif n'écrit aucun code et n'exécute aucun test, mais le pipeline contient encore des étapes d'implémentation ou de test. Supprimez-les ou
|
|
4906
|
+
"purposeStepsConflict": "Cet objectif n'écrit aucun code et n'exécute aucun test, mais le pipeline contient encore des étapes d'implémentation ou de test. Supprimez-les ou choisissez un objectif qui livre du code (Développement ou Correction de bug).",
|
|
4906
4907
|
"promptEditTooltip": "Modifier l'invite système de cet agent",
|
|
4907
4908
|
"promptEditedTooltip": "L'invite système de cet agent est modifiée pour cet espace de travail",
|
|
4908
4909
|
"consensusGroups": "Groupes de consensus",
|
package/i18n/locales/he.json
CHANGED
|
@@ -4834,6 +4834,7 @@
|
|
|
4834
4834
|
"purposeUnrecognized": "לא מזוהה ({purpose})",
|
|
4835
4835
|
"purposeOption": {
|
|
4836
4836
|
"build": "פיתוח",
|
|
4837
|
+
"bugfix": "תיקון באג",
|
|
4837
4838
|
"document": "תיעוד",
|
|
4838
4839
|
"review": "סקירה",
|
|
4839
4840
|
"research": "מחקר",
|
|
@@ -4902,7 +4903,7 @@
|
|
|
4902
4903
|
"skillNoneAvailable": "אין כישורים זמינים. קישר מקור כישורים בהגדרות החשבון.",
|
|
4903
4904
|
"skillMissing": "כישור זה כבר אינו בקטלוג; בחר אחר.",
|
|
4904
4905
|
"skillNeedsPick": "שלב Skill דורש כישור נבחר לפני שתוכל לשמור.",
|
|
4905
|
-
"purposeStepsConflict": "מטרה זו אינה כותבת קוד ואינה מריצה בדיקות, אך הצינור עדיין כולל שלבי פיתוח או בדיקה. הסירו אותם או
|
|
4906
|
+
"purposeStepsConflict": "מטרה זו אינה כותבת קוד ואינה מריצה בדיקות, אך הצינור עדיין כולל שלבי פיתוח או בדיקה. הסירו אותם או בחרו מטרה שמספקת קוד (פיתוח או תיקון באג).",
|
|
4906
4907
|
"promptEditTooltip": "עריכת הנחיית המערכת של הסוכן הזה",
|
|
4907
4908
|
"promptEditedTooltip": "הנחיית המערכת של הסוכן הזה נערכה עבור מרחב העבודה הזה",
|
|
4908
4909
|
"consensusGroups": "קבוצות קונצנזוס",
|
package/i18n/locales/it.json
CHANGED
|
@@ -4391,6 +4391,7 @@
|
|
|
4391
4391
|
"purposeUnrecognized": "Non riconosciuto ({purpose})",
|
|
4392
4392
|
"purposeOption": {
|
|
4393
4393
|
"build": "Sviluppo",
|
|
4394
|
+
"bugfix": "Correzione di bug",
|
|
4394
4395
|
"document": "Documentazione",
|
|
4395
4396
|
"review": "Revisione",
|
|
4396
4397
|
"research": "Ricerca",
|
|
@@ -4459,7 +4460,7 @@
|
|
|
4459
4460
|
"skillNoneAvailable": "Nessuna competenza disponibile. Collega una fonte di competenze nelle impostazioni dell'account.",
|
|
4460
4461
|
"skillMissing": "Questa competenza non è più nel catalogo; scegline un'altra.",
|
|
4461
4462
|
"skillNeedsPick": "Un passo Skill richiede una competenza selezionata prima di poter salvare.",
|
|
4462
|
-
"purposeStepsConflict": "Questo scopo non scrive codice né esegue test, ma la pipeline contiene ancora passaggi di implementazione o di test. Rimuovili
|
|
4463
|
+
"purposeStepsConflict": "Questo scopo non scrive codice né esegue test, ma la pipeline contiene ancora passaggi di implementazione o di test. Rimuovili oppure scegli uno scopo che rilascia codice (Sviluppo o Correzione di bug).",
|
|
4463
4464
|
"promptEditTooltip": "Modifica il prompt di sistema di questo agente",
|
|
4464
4465
|
"promptEditedTooltip": "Il prompt di sistema di questo agente è modificato per questo spazio di lavoro",
|
|
4465
4466
|
"consensusGroups": "Gruppi di consenso",
|
package/i18n/locales/ja.json
CHANGED
|
@@ -4834,6 +4834,7 @@
|
|
|
4834
4834
|
"purposeUnrecognized": "認識できません ({purpose})",
|
|
4835
4835
|
"purposeOption": {
|
|
4836
4836
|
"build": "開発",
|
|
4837
|
+
"bugfix": "バグ修正",
|
|
4837
4838
|
"document": "ドキュメント",
|
|
4838
4839
|
"review": "レビュー",
|
|
4839
4840
|
"research": "調査",
|
|
@@ -4902,7 +4903,7 @@
|
|
|
4902
4903
|
"skillNoneAvailable": "利用できるスキルがありません。アカウント設定でスキルソースをリンクしてください。",
|
|
4903
4904
|
"skillMissing": "このスキルはカタログに存在しません。別のスキルを選んでください。",
|
|
4904
4905
|
"skillNeedsPick": "Skill ステップを保存するにはスキルの選択が必要です。",
|
|
4905
|
-
"purposeStepsConflict": "
|
|
4906
|
+
"purposeStepsConflict": "この目的はコードを書かず、テストも実行しませんが、パイプラインには実装またはテストのステップが残っています。それらを削除するか、コードを提供する目的(「開発」または「バグ修正」)に切り替えてください。",
|
|
4906
4907
|
"promptEditTooltip": "このエージェントのシステムプロンプトを編集",
|
|
4907
4908
|
"promptEditedTooltip": "このエージェントのシステムプロンプトは、このワークスペース用に編集されています",
|
|
4908
4909
|
"consensusGroups": "コンセンサスグループ",
|
package/i18n/locales/pl.json
CHANGED
|
@@ -4834,6 +4834,7 @@
|
|
|
4834
4834
|
"purposeUnrecognized": "Nierozpoznany ({purpose})",
|
|
4835
4835
|
"purposeOption": {
|
|
4836
4836
|
"build": "Programowanie",
|
|
4837
|
+
"bugfix": "Poprawka błędu",
|
|
4837
4838
|
"document": "Dokumentacja",
|
|
4838
4839
|
"review": "Przegląd",
|
|
4839
4840
|
"research": "Badania",
|
|
@@ -4902,7 +4903,7 @@
|
|
|
4902
4903
|
"skillNoneAvailable": "Brak dostępnych umiejętności. Połącz źródło umiejętności w ustawieniach konta.",
|
|
4903
4904
|
"skillMissing": "Tej umiejętności nie ma już w katalogu; wybierz inną.",
|
|
4904
4905
|
"skillNeedsPick": "Krok Skill wymaga wybranej umiejętności, zanim będzie można zapisać.",
|
|
4905
|
-
"purposeStepsConflict": "To przeznaczenie nie tworzy kodu ani nie uruchamia testów, ale potok nadal zawiera kroki programowania lub testowania. Usuń je lub zmień przeznaczenie na Programowanie.",
|
|
4906
|
+
"purposeStepsConflict": "To przeznaczenie nie tworzy kodu ani nie uruchamia testów, ale potok nadal zawiera kroki programowania lub testowania. Usuń je lub zmień przeznaczenie na takie, które dostarcza kod (Programowanie lub Poprawka błędu).",
|
|
4906
4907
|
"promptEditTooltip": "Edytuj prompt systemowy tego agenta",
|
|
4907
4908
|
"promptEditedTooltip": "Prompt systemowy tego agenta jest zmieniony dla tego obszaru roboczego",
|
|
4908
4909
|
"consensusGroups": "Grupy konsensusu",
|
package/i18n/locales/tr.json
CHANGED
|
@@ -4834,6 +4834,7 @@
|
|
|
4834
4834
|
"purposeUnrecognized": "Tanınmayan ({purpose})",
|
|
4835
4835
|
"purposeOption": {
|
|
4836
4836
|
"build": "Geliştirme",
|
|
4837
|
+
"bugfix": "Hata düzeltme",
|
|
4837
4838
|
"document": "Dokümantasyon",
|
|
4838
4839
|
"review": "İnceleme",
|
|
4839
4840
|
"research": "Araştırma",
|
|
@@ -4902,7 +4903,7 @@
|
|
|
4902
4903
|
"skillNoneAvailable": "Kullanılabilir beceri yok. Hesap ayarlarında bir beceri kaynağı bağlayın.",
|
|
4903
4904
|
"skillMissing": "Bu beceri artık katalogda yok; başka birini seçin.",
|
|
4904
4905
|
"skillNeedsPick": "Bir Skill adımı, kaydedebilmeniz için seçilmiş bir beceri gerektirir.",
|
|
4905
|
-
"purposeStepsConflict": "Bu amaç kod yazmaz ve test çalıştırmaz, ancak işlem hattında hâlâ uygulama veya test adımları var. Bunları kaldırın veya
|
|
4906
|
+
"purposeStepsConflict": "Bu amaç kod yazmaz ve test çalıştırmaz, ancak işlem hattında hâlâ uygulama veya test adımları var. Bunları kaldırın veya kod teslim eden bir amaca geçin (Geliştirme ya da Hata düzeltme).",
|
|
4906
4907
|
"promptEditTooltip": "Bu ajanın sistem istemini düzenle",
|
|
4907
4908
|
"promptEditedTooltip": "Bu ajanın sistem istemi bu çalışma alanı için düzenlenmiş",
|
|
4908
4909
|
"consensusGroups": "Uzlaşı grupları",
|
package/i18n/locales/uk.json
CHANGED
|
@@ -4834,6 +4834,7 @@
|
|
|
4834
4834
|
"purposeUnrecognized": "Нерозпізнано ({purpose})",
|
|
4835
4835
|
"purposeOption": {
|
|
4836
4836
|
"build": "Розробка",
|
|
4837
|
+
"bugfix": "Виправлення помилки",
|
|
4837
4838
|
"document": "Документація",
|
|
4838
4839
|
"review": "Рецензування",
|
|
4839
4840
|
"research": "Дослідження",
|
|
@@ -4902,7 +4903,7 @@
|
|
|
4902
4903
|
"skillNoneAvailable": "Немає доступних навичок. Пов'яжіть джерело навичок у налаштуваннях облікового запису.",
|
|
4903
4904
|
"skillMissing": "Цієї навички більше немає в каталозі; виберіть іншу.",
|
|
4904
4905
|
"skillNeedsPick": "Крок Skill потребує вибраної навички, перш ніж ви зможете зберегти.",
|
|
4905
|
-
"purposeStepsConflict": "Це призначення не пише код і не запускає тести, але конвеєр досі містить кроки програмування або тестування. Видаліть їх або змініть призначення на
|
|
4906
|
+
"purposeStepsConflict": "Це призначення не пише код і не запускає тести, але конвеєр досі містить кроки програмування або тестування. Видаліть їх або змініть призначення на таке, що постачає код (Розробка або Виправлення помилки).",
|
|
4906
4907
|
"promptEditTooltip": "Редагувати системний промпт цього агента",
|
|
4907
4908
|
"promptEditedTooltip": "Системний промпт цього агента змінено для цього робочого простору",
|
|
4908
4909
|
"consensusGroups": "Групи консенсусу",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cat-factory/app",
|
|
3
|
-
"version": "0.270.
|
|
3
|
+
"version": "0.270.3",
|
|
4
4
|
"description": "Reusable Nuxt layer for the Agent Architecture Board SPA (components, stores, composables, pages). Consume it from a thin deployment app via `extends: ['@cat-factory/app']` and point it at your backend with NUXT_PUBLIC_API_BASE. See deploy/frontend for an example.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
"valibot": "^1.4.2",
|
|
41
41
|
"vue": "3.5.41",
|
|
42
42
|
"wretch": "^3.0.9",
|
|
43
|
-
"@cat-factory/contracts": "0.
|
|
43
|
+
"@cat-factory/contracts": "0.309.0"
|
|
44
44
|
},
|
|
45
45
|
"devDependencies": {
|
|
46
46
|
"@toad-contracts/testing": "0.3.2",
|