@cat-factory/app 1.0.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.
- package/LICENSE +21 -0
- package/README.md +88 -0
- package/app/app.config.ts +8 -0
- package/app/app.vue +11 -0
- package/app/assets/css/main.css +100 -0
- package/app/components/auth/AuthGate.vue +24 -0
- package/app/components/auth/LoginScreen.vue +18 -0
- package/app/components/auth/UserMenu.vue +39 -0
- package/app/components/board/AgentFailureCard.vue +97 -0
- package/app/components/board/AgentStopButton.vue +61 -0
- package/app/components/board/BoardCanvas.vue +146 -0
- package/app/components/board/TaskDependencyEdges.vue +132 -0
- package/app/components/board/nodes/AgentChip.vue +59 -0
- package/app/components/board/nodes/BlockNode.vue +347 -0
- package/app/components/board/nodes/DecisionBadge.vue +21 -0
- package/app/components/board/nodes/DraggableTask.vue +69 -0
- package/app/components/board/nodes/ModuleFrame.vue +70 -0
- package/app/components/board/nodes/TaskCard.vue +237 -0
- package/app/components/bootstrap/BootstrapModal.vue +665 -0
- package/app/components/documents/DocumentImportModal.vue +161 -0
- package/app/components/documents/DocumentSourceConnectModal.vue +127 -0
- package/app/components/documents/SpawnPreviewModal.vue +161 -0
- package/app/components/documents/TaskContextDocs.vue +83 -0
- package/app/components/focus/BlockFocusView.vue +161 -0
- package/app/components/fragments/FragmentLibraryPanel.vue +340 -0
- package/app/components/github/GitHubConnect.vue +183 -0
- package/app/components/github/GitHubPanel.vue +584 -0
- package/app/components/layout/BoardSwitcher.vue +202 -0
- package/app/components/layout/BoardToolbar.vue +109 -0
- package/app/components/layout/SideBar.vue +193 -0
- package/app/components/layout/SpendWarningBanner.vue +107 -0
- package/app/components/palettes/AgentPalette.vue +33 -0
- package/app/components/palettes/BlockPalette.vue +41 -0
- package/app/components/palettes/PipelinePalette.vue +74 -0
- package/app/components/panels/DecisionModal.vue +71 -0
- package/app/components/panels/InspectorPanel.vue +296 -0
- package/app/components/panels/inspector/ContainerSummary.vue +74 -0
- package/app/components/panels/inspector/TaskDependencies.vue +70 -0
- package/app/components/panels/inspector/TaskExecution.vue +175 -0
- package/app/components/panels/inspector/TaskModelSettings.vue +128 -0
- package/app/components/panels/inspector/TaskStructure.vue +139 -0
- package/app/components/pipeline/PipelineBuilder.vue +227 -0
- package/app/components/pipeline/PipelineProgress.vue +246 -0
- package/app/components/requirements/RequirementReviewModal.vue +328 -0
- package/app/components/scenarios/FeatureScenarios.vue +162 -0
- package/app/components/scenarios/ScenarioCard.vue +109 -0
- package/app/components/tasks/TaskContextIssues.vue +88 -0
- package/app/components/tasks/TaskImportModal.vue +140 -0
- package/app/components/tasks/TaskSourceConnectModal.vue +122 -0
- package/app/composables/useApi.ts +535 -0
- package/app/composables/useBlockDrag.ts +75 -0
- package/app/composables/useBlockQueries.ts +136 -0
- package/app/composables/useBoardFlow.ts +11 -0
- package/app/composables/useDepLabels.ts +26 -0
- package/app/composables/useSemanticZoom.ts +16 -0
- package/app/composables/useWorkspaceStream.ts +125 -0
- package/app/docs/architecture.md +31 -0
- package/app/pages/index.vue +80 -0
- package/app/stores/accounts.ts +64 -0
- package/app/stores/agentRuns.ts +117 -0
- package/app/stores/agents.ts +40 -0
- package/app/stores/auth.ts +97 -0
- package/app/stores/board.spec.ts +197 -0
- package/app/stores/board.ts +147 -0
- package/app/stores/bootstrap.ts +97 -0
- package/app/stores/documents.ts +165 -0
- package/app/stores/execution.ts +115 -0
- package/app/stores/fragmentLibrary.ts +147 -0
- package/app/stores/fragments.ts +40 -0
- package/app/stores/github.ts +291 -0
- package/app/stores/models.ts +48 -0
- package/app/stores/pipelines.ts +77 -0
- package/app/stores/requirements.ts +133 -0
- package/app/stores/scenarios.spec.ts +82 -0
- package/app/stores/scenarios.ts +196 -0
- package/app/stores/tasks.spec.ts +71 -0
- package/app/stores/tasks.ts +149 -0
- package/app/stores/ui.ts +204 -0
- package/app/stores/workspace.ts +201 -0
- package/app/types/accounts.ts +38 -0
- package/app/types/bootstrap.ts +83 -0
- package/app/types/documents.ts +92 -0
- package/app/types/domain.ts +216 -0
- package/app/types/execution.ts +110 -0
- package/app/types/fragments.ts +72 -0
- package/app/types/github.ts +153 -0
- package/app/types/models.ts +48 -0
- package/app/types/requirements.ts +38 -0
- package/app/types/scenarios.ts +36 -0
- package/app/types/tasks.ts +67 -0
- package/app/utils/catalog.spec.ts +82 -0
- package/app/utils/catalog.ts +185 -0
- package/app/utils/dnd.ts +29 -0
- package/nuxt.config.ts +43 -0
- package/package.json +43 -0
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import { defineStore } from 'pinia'
|
|
2
|
+
import { computed, ref } from 'vue'
|
|
3
|
+
import type { SpendStatus, Workspace, WorkspaceSnapshot } from '~/types/domain'
|
|
4
|
+
import { useAccountsStore } from '~/stores/accounts'
|
|
5
|
+
import { useBoardStore } from '~/stores/board'
|
|
6
|
+
import { usePipelinesStore } from '~/stores/pipelines'
|
|
7
|
+
import { useExecutionStore } from '~/stores/execution'
|
|
8
|
+
import { useAgentRunsStore } from '~/stores/agentRuns'
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Owns the active workspace and bootstraps the app against the backend. On load
|
|
12
|
+
* it resolves the user's accounts, lists the boards in the active account, opens
|
|
13
|
+
* the persisted one (or the first, or a fresh seeded board), and hydrates the
|
|
14
|
+
* board / pipelines / execution stores from its snapshot.
|
|
15
|
+
*
|
|
16
|
+
* Boards are scoped to an account: switching account re-scopes the board list,
|
|
17
|
+
* and new boards are stamped with the active account so a team can keep org
|
|
18
|
+
* boards separate from personal ones. Only the active workspace id is persisted —
|
|
19
|
+
* all board data lives on the server.
|
|
20
|
+
*/
|
|
21
|
+
export const useWorkspaceStore = defineStore(
|
|
22
|
+
'workspace',
|
|
23
|
+
() => {
|
|
24
|
+
const api = useApi()
|
|
25
|
+
|
|
26
|
+
/** Active workspace id (persisted so a reload reopens the same board). */
|
|
27
|
+
const workspaceId = ref<string | null>(null)
|
|
28
|
+
/** Every board visible to the user, across the accounts they belong to. */
|
|
29
|
+
const workspaces = ref<Workspace[]>([])
|
|
30
|
+
/** True once the initial snapshot has been loaded and stores hydrated. */
|
|
31
|
+
const ready = ref(false)
|
|
32
|
+
/** Set when bootstrap fails so the UI can show a retry. */
|
|
33
|
+
const error = ref<string | null>(null)
|
|
34
|
+
/** Latest spend-safeguard status from the server (null until first load). */
|
|
35
|
+
const spend = ref<SpendStatus | null>(null)
|
|
36
|
+
|
|
37
|
+
/** The boards belonging to the active account (all boards when auth is off). */
|
|
38
|
+
const accountWorkspaces = computed(() => {
|
|
39
|
+
const accounts = useAccountsStore()
|
|
40
|
+
if (!accounts.enabled || !accounts.activeAccountId) return workspaces.value
|
|
41
|
+
return workspaces.value.filter((w) => w.accountId === accounts.activeAccountId)
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
/** The active board's row (for the switcher label). */
|
|
45
|
+
const activeWorkspace = computed(
|
|
46
|
+
() => workspaces.value.find((w) => w.id === workspaceId.value) ?? null,
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
/** Push a snapshot into the data stores. */
|
|
50
|
+
function hydrate(snapshot: WorkspaceSnapshot) {
|
|
51
|
+
workspaceId.value = snapshot.workspace.id
|
|
52
|
+
spend.value = snapshot.spend ?? null
|
|
53
|
+
// Keep the board list in step (e.g. a freshly created board, or a rename).
|
|
54
|
+
const i = workspaces.value.findIndex((w) => w.id === snapshot.workspace.id)
|
|
55
|
+
if (i >= 0) workspaces.value[i] = snapshot.workspace
|
|
56
|
+
else workspaces.value.unshift(snapshot.workspace)
|
|
57
|
+
useBoardStore().hydrate(snapshot.blocks)
|
|
58
|
+
usePipelinesStore().hydrate(snapshot.pipelines)
|
|
59
|
+
useExecutionStore().hydrate(snapshot.executions)
|
|
60
|
+
useAgentRunsStore().hydrate(snapshot.bootstrapJobs ?? [])
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** Resolve accounts + boards, then open the right board for the active account. */
|
|
64
|
+
async function init() {
|
|
65
|
+
ready.value = false
|
|
66
|
+
error.value = null
|
|
67
|
+
try {
|
|
68
|
+
// Accounts are an auth concept — empty in dev, which leaves boards unscoped.
|
|
69
|
+
await useAccountsStore()
|
|
70
|
+
.load()
|
|
71
|
+
.catch(() => {})
|
|
72
|
+
workspaces.value = await api.listWorkspaces()
|
|
73
|
+
await resolveActiveBoard()
|
|
74
|
+
ready.value = true
|
|
75
|
+
} catch (e) {
|
|
76
|
+
error.value = e instanceof Error ? e.message : 'Failed to reach the backend.'
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** Open the persisted board (aligning the active account to it), else pick/create one. */
|
|
81
|
+
async function resolveActiveBoard() {
|
|
82
|
+
const accounts = useAccountsStore()
|
|
83
|
+
if (workspaceId.value) {
|
|
84
|
+
const existing = workspaces.value.find((w) => w.id === workspaceId.value)
|
|
85
|
+
if (existing) {
|
|
86
|
+
if (accounts.enabled && existing.accountId) accounts.activeAccountId = existing.accountId
|
|
87
|
+
hydrate(await api.getWorkspace(existing.id))
|
|
88
|
+
return
|
|
89
|
+
}
|
|
90
|
+
// Persisted board is gone (deleted, or now another tenant's) — fall through.
|
|
91
|
+
workspaceId.value = null
|
|
92
|
+
}
|
|
93
|
+
const first = accountWorkspaces.value[0]
|
|
94
|
+
if (first) {
|
|
95
|
+
hydrate(await api.getWorkspace(first.id))
|
|
96
|
+
} else {
|
|
97
|
+
hydrate(
|
|
98
|
+
await api.createWorkspace({
|
|
99
|
+
seed: true,
|
|
100
|
+
accountId: accounts.activeAccountId ?? undefined,
|
|
101
|
+
}),
|
|
102
|
+
)
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** Switch to another board (within reach of the active account). */
|
|
107
|
+
async function switchTo(id: string) {
|
|
108
|
+
if (id === workspaceId.value) return
|
|
109
|
+
hydrate(await api.getWorkspace(id))
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** Switch the active account, then open one of its boards (creating one if needed). */
|
|
113
|
+
async function selectAccount(id: string) {
|
|
114
|
+
const accounts = useAccountsStore()
|
|
115
|
+
if (id === accounts.activeAccountId) return
|
|
116
|
+
accounts.switchTo(id)
|
|
117
|
+
workspaceId.value = null
|
|
118
|
+
await resolveActiveBoard()
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/** Create a new board in the active account and open it. */
|
|
122
|
+
async function create(name?: string) {
|
|
123
|
+
const accounts = useAccountsStore()
|
|
124
|
+
const snapshot = await api.createWorkspace({
|
|
125
|
+
seed: true,
|
|
126
|
+
name,
|
|
127
|
+
accountId: accounts.activeAccountId ?? undefined,
|
|
128
|
+
})
|
|
129
|
+
hydrate(snapshot)
|
|
130
|
+
return snapshot.workspace
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/** Rename a board. */
|
|
134
|
+
async function rename(id: string, name: string) {
|
|
135
|
+
const updated = await api.renameWorkspace(id, name)
|
|
136
|
+
const i = workspaces.value.findIndex((w) => w.id === id)
|
|
137
|
+
if (i >= 0) workspaces.value[i] = updated
|
|
138
|
+
return updated
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/** Delete a board; if it was active, fall back to another in the account. */
|
|
142
|
+
async function remove(id: string) {
|
|
143
|
+
await api.deleteWorkspace(id)
|
|
144
|
+
workspaces.value = workspaces.value.filter((w) => w.id !== id)
|
|
145
|
+
if (workspaceId.value === id) {
|
|
146
|
+
workspaceId.value = null
|
|
147
|
+
await resolveActiveBoard()
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/** Re-fetch the snapshot and re-hydrate (after mutations and on stream (re)connect). */
|
|
152
|
+
async function refresh() {
|
|
153
|
+
if (!workspaceId.value) return
|
|
154
|
+
hydrate(await api.getWorkspace(workspaceId.value))
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/** Discard the current workspace and start a fresh, seeded one in this account. */
|
|
158
|
+
async function reset() {
|
|
159
|
+
const prev = workspaceId.value
|
|
160
|
+
workspaceId.value = null
|
|
161
|
+
await create()
|
|
162
|
+
if (prev) {
|
|
163
|
+
workspaces.value = workspaces.value.filter((w) => w.id !== prev)
|
|
164
|
+
await api.deleteWorkspace(prev).catch(() => {})
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/** The active workspace id, or throw if the app isn't bootstrapped yet. */
|
|
169
|
+
function requireId(): string {
|
|
170
|
+
if (!workspaceId.value) throw new Error('No active workspace')
|
|
171
|
+
return workspaceId.value
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/** Resume runs paused by the spend safeguard, then refresh the snapshot. */
|
|
175
|
+
async function resumeSpend() {
|
|
176
|
+
await api.resumeSpend(requireId())
|
|
177
|
+
await refresh()
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return {
|
|
181
|
+
workspaceId,
|
|
182
|
+
workspaces,
|
|
183
|
+
accountWorkspaces,
|
|
184
|
+
activeWorkspace,
|
|
185
|
+
ready,
|
|
186
|
+
error,
|
|
187
|
+
spend,
|
|
188
|
+
init,
|
|
189
|
+
switchTo,
|
|
190
|
+
selectAccount,
|
|
191
|
+
create,
|
|
192
|
+
rename,
|
|
193
|
+
remove,
|
|
194
|
+
refresh,
|
|
195
|
+
reset,
|
|
196
|
+
requireId,
|
|
197
|
+
resumeSpend,
|
|
198
|
+
}
|
|
199
|
+
},
|
|
200
|
+
{ persist: { pick: ['workspaceId'] } },
|
|
201
|
+
)
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// Account tenancy. An account owns workspaces (boards): either a single user's
|
|
3
|
+
// `personal` account or an `org` shared by many engineers. Memberships map users
|
|
4
|
+
// to accounts with a role. Mirrors the `@cat-factory/contracts` account schemas
|
|
5
|
+
// so responses drop straight into the Pinia store.
|
|
6
|
+
// ---------------------------------------------------------------------------
|
|
7
|
+
|
|
8
|
+
export type AccountType = 'personal' | 'org'
|
|
9
|
+
export type AccountRole = 'owner' | 'member'
|
|
10
|
+
|
|
11
|
+
/** An account, annotated with the signed-in caller's role in it. */
|
|
12
|
+
export interface Account {
|
|
13
|
+
id: string
|
|
14
|
+
type: AccountType
|
|
15
|
+
name: string
|
|
16
|
+
githubAccountLogin: string | null
|
|
17
|
+
createdAt: number
|
|
18
|
+
/** The caller's role in this account (`null` in the auth-disabled path). */
|
|
19
|
+
role: AccountRole | null
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** A member of an account. */
|
|
23
|
+
export interface AccountMember {
|
|
24
|
+
accountId: string
|
|
25
|
+
userId: number
|
|
26
|
+
role: AccountRole
|
|
27
|
+
createdAt: number
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface CreateAccountInput {
|
|
31
|
+
name: string
|
|
32
|
+
githubAccountLogin?: string
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface AddMemberInput {
|
|
36
|
+
userId: number
|
|
37
|
+
role?: AccountRole
|
|
38
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// Repo-bootstrap domain types. Mirrors the `@cat-factory/contracts` bootstrap
|
|
3
|
+
// schemas so backend payloads drop straight into the Pinia store.
|
|
4
|
+
//
|
|
5
|
+
// A "reference architecture" is a managed base repo (an opinionated starter the
|
|
6
|
+
// org wants new services to follow); the "bootstrap repo" task creates a new repo
|
|
7
|
+
// from one and runs a bootstrapper agent in a container to adapt it.
|
|
8
|
+
// ---------------------------------------------------------------------------
|
|
9
|
+
|
|
10
|
+
import type { AgentFailure, AgentFailureKind, StepSubtasks } from './execution'
|
|
11
|
+
|
|
12
|
+
/** A managed base repository new repos are bootstrapped from. */
|
|
13
|
+
export interface ReferenceArchitecture {
|
|
14
|
+
id: string
|
|
15
|
+
workspaceId: string
|
|
16
|
+
name: string
|
|
17
|
+
description: string
|
|
18
|
+
repoOwner: string
|
|
19
|
+
repoName: string
|
|
20
|
+
defaultInstructions: string
|
|
21
|
+
createdAt: number
|
|
22
|
+
updatedAt: number
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Body to register a reference architecture. */
|
|
26
|
+
export interface CreateReferenceArchitectureInput {
|
|
27
|
+
name: string
|
|
28
|
+
description?: string
|
|
29
|
+
repoOwner: string
|
|
30
|
+
repoName: string
|
|
31
|
+
defaultInstructions?: string
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** Body to patch a reference architecture (only supplied fields change). */
|
|
35
|
+
export type UpdateReferenceArchitectureInput = Partial<CreateReferenceArchitectureInput>
|
|
36
|
+
|
|
37
|
+
/** Lifecycle of a single "bootstrap repo" run. */
|
|
38
|
+
export type BootstrapStatus = 'pending' | 'running' | 'succeeded' | 'failed'
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* A bootstrap run's failure is now the shared {@link AgentFailure} (same shape
|
|
42
|
+
* execution runs use), so the board renders one failure banner + retry for any
|
|
43
|
+
* agent. These aliases stay for back-compat / documentation of the bootstrap
|
|
44
|
+
* subset; `bootstrapFailureKindSchema` on the backend stays narrow.
|
|
45
|
+
*/
|
|
46
|
+
export type BootstrapFailureKind = AgentFailureKind
|
|
47
|
+
|
|
48
|
+
/** Structured failure diagnostics captured when a bootstrap run fails. */
|
|
49
|
+
export type BootstrapFailure = AgentFailure
|
|
50
|
+
|
|
51
|
+
/** One "bootstrap repo" run with its outcome. */
|
|
52
|
+
export interface BootstrapJob {
|
|
53
|
+
id: string
|
|
54
|
+
workspaceId: string
|
|
55
|
+
/** Reference architecture the run was based on, or null for a from-scratch run. */
|
|
56
|
+
referenceArchitectureId: string | null
|
|
57
|
+
/** Denormalized reference architecture name, or null for a from-scratch run. */
|
|
58
|
+
referenceArchitectureName: string | null
|
|
59
|
+
repoName: string
|
|
60
|
+
repoOwner: string | null
|
|
61
|
+
repoUrl: string | null
|
|
62
|
+
instructions: string
|
|
63
|
+
status: BootstrapStatus
|
|
64
|
+
/** The board service frame this run materialises, or null if none was created. */
|
|
65
|
+
blockId: string | null
|
|
66
|
+
/** Live subtask counts from the bootstrapper agent, or null until it reports. */
|
|
67
|
+
subtasks: StepSubtasks | null
|
|
68
|
+
error: string | null
|
|
69
|
+
/** Structured failure diagnostics when `status` is `failed`; null otherwise. */
|
|
70
|
+
failure: BootstrapFailure | null
|
|
71
|
+
createdAt: number
|
|
72
|
+
updatedAt: number
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** Body to kick off a bootstrap run. Omit `referenceArchitectureId` to bootstrap
|
|
76
|
+
* from a freeform prompt alone (then `instructions` must be non-empty). */
|
|
77
|
+
export interface BootstrapRepoInput {
|
|
78
|
+
referenceArchitectureId?: string | null
|
|
79
|
+
repoName: string
|
|
80
|
+
description?: string
|
|
81
|
+
private?: boolean
|
|
82
|
+
instructions?: string
|
|
83
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// Document-source integration. Requirements / RFCs / PRDs imported from external
|
|
3
|
+
// sources (Confluence, Notion, …) can be expanded into board structure or
|
|
4
|
+
// attached to a task as agent context. These mirror the `@cat-factory/contracts`
|
|
5
|
+
// document schemas; the abstraction is source-agnostic, keyed by `source`.
|
|
6
|
+
// ---------------------------------------------------------------------------
|
|
7
|
+
|
|
8
|
+
import type { BlockType } from './domain'
|
|
9
|
+
|
|
10
|
+
/** The external document sources cat-factory can link to. */
|
|
11
|
+
export type DocumentSourceKind = 'confluence' | 'notion'
|
|
12
|
+
|
|
13
|
+
/** One credential a provider needs to connect (rendered as a form field). */
|
|
14
|
+
export interface CredentialField {
|
|
15
|
+
key: string
|
|
16
|
+
label: string
|
|
17
|
+
help?: string
|
|
18
|
+
placeholder?: string
|
|
19
|
+
secret?: boolean
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** A source's self-description: drives the generic connect + import UI. */
|
|
23
|
+
export interface DocumentSourceDescriptor {
|
|
24
|
+
source: DocumentSourceKind
|
|
25
|
+
label: string
|
|
26
|
+
/** Lucide icon name for the source. */
|
|
27
|
+
icon: string
|
|
28
|
+
credentialFields: CredentialField[]
|
|
29
|
+
refLabel: string
|
|
30
|
+
refPlaceholder: string
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** A workspace's connection to a document source (never carries credentials). */
|
|
34
|
+
export interface DocumentConnection {
|
|
35
|
+
source: DocumentSourceKind
|
|
36
|
+
/** Human-friendly label for what we're connected to (site URL, workspace name). */
|
|
37
|
+
label: string
|
|
38
|
+
/** When the connection was established (epoch ms). */
|
|
39
|
+
connectedAt: number
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** A page imported from a source into the workspace. */
|
|
43
|
+
export interface SourceDocument {
|
|
44
|
+
source: DocumentSourceKind
|
|
45
|
+
/** The source's stable id for the page. */
|
|
46
|
+
externalId: string
|
|
47
|
+
title: string
|
|
48
|
+
url: string
|
|
49
|
+
/** Short plain-text preview of the page body. */
|
|
50
|
+
excerpt: string
|
|
51
|
+
/** The board block this document is attached to as context, if any. */
|
|
52
|
+
linkedBlockId: string | null
|
|
53
|
+
syncedAt: number
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** A proposed task within a planned frame/module. */
|
|
57
|
+
export interface PlanTask {
|
|
58
|
+
title: string
|
|
59
|
+
description?: string
|
|
60
|
+
features?: string[]
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** A proposed module grouping tasks within a planned frame. */
|
|
64
|
+
export interface PlanModule {
|
|
65
|
+
name: string
|
|
66
|
+
tasks: PlanTask[]
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** A proposed top-level frame with its modules and loose tasks. */
|
|
70
|
+
export interface PlanFrame {
|
|
71
|
+
type: BlockType
|
|
72
|
+
title: string
|
|
73
|
+
description?: string
|
|
74
|
+
modules: PlanModule[]
|
|
75
|
+
tasks: PlanTask[]
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** A board structure extracted from an imported document. */
|
|
79
|
+
export interface DocumentBoardPlan {
|
|
80
|
+
source: DocumentSourceKind
|
|
81
|
+
externalId: string
|
|
82
|
+
/** Whether an LLM produced the plan or the deterministic heading parser did. */
|
|
83
|
+
planner: 'llm' | 'headings'
|
|
84
|
+
frames: PlanFrame[]
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** Counts of blocks created by spawning a plan onto the board. */
|
|
88
|
+
export interface SpawnResult {
|
|
89
|
+
frames: number
|
|
90
|
+
modules: number
|
|
91
|
+
tasks: number
|
|
92
|
+
}
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// Domain model for the Agent Architecture Board.
|
|
3
|
+
//
|
|
4
|
+
// These shapes mirror the `@cat-factory/contracts` wire schemas exactly, so a
|
|
5
|
+
// payload returned by the backend drops straight into the Pinia stores without
|
|
6
|
+
// translation. The board and its agent pipelines are owned by the backend; the
|
|
7
|
+
// frontend renders and mutates that state over the REST API.
|
|
8
|
+
//
|
|
9
|
+
// This module holds the core board vocabulary. Adjacent concerns live in
|
|
10
|
+
// sibling modules and are re-exported below so `~/types/domain` stays the single
|
|
11
|
+
// import surface:
|
|
12
|
+
// - execution model → ./execution
|
|
13
|
+
// - models/fragments → ./models
|
|
14
|
+
// - document sources → ./documents
|
|
15
|
+
// ---------------------------------------------------------------------------
|
|
16
|
+
|
|
17
|
+
import type { ExecutionInstance } from './execution'
|
|
18
|
+
import type { BootstrapJob } from './bootstrap'
|
|
19
|
+
|
|
20
|
+
/** Lifecycle of an architecture building block. */
|
|
21
|
+
export type BlockStatus =
|
|
22
|
+
| 'planned' // sketched, no dependencies satisfied yet
|
|
23
|
+
| 'ready' // dependencies done, can be implemented
|
|
24
|
+
| 'in_progress' // a pipeline is (visually) running against it
|
|
25
|
+
| 'blocked' // a pipeline step is waiting on a human decision
|
|
26
|
+
| 'pr_ready' // pipeline finished — a PR is open and awaiting merge
|
|
27
|
+
| 'done' // PR merged, implementation complete
|
|
28
|
+
|
|
29
|
+
/** Kind of architecture building block (drives icon + accent). */
|
|
30
|
+
export type BlockType =
|
|
31
|
+
| 'frontend'
|
|
32
|
+
| 'service'
|
|
33
|
+
| 'api'
|
|
34
|
+
| 'database'
|
|
35
|
+
| 'queue'
|
|
36
|
+
| 'integration'
|
|
37
|
+
| 'external'
|
|
38
|
+
| 'environment'
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Where a block sits in the granularity hierarchy. Both `frame` and `module`
|
|
42
|
+
* are containers ("frames") that hold draggable tasks:
|
|
43
|
+
* - `frame` a top-level Service; the only level rendered as a board node
|
|
44
|
+
* - `module` a sub-frame inside a service; created when a task assigned to a
|
|
45
|
+
* not-yet-existing module is implemented (tasks can also be dragged in)
|
|
46
|
+
* - `task` a draggable unit of work living inside a service or a module
|
|
47
|
+
*/
|
|
48
|
+
export type BlockLevel = 'frame' | 'module' | 'task'
|
|
49
|
+
|
|
50
|
+
/** A building block dropped on the board. */
|
|
51
|
+
export interface Block {
|
|
52
|
+
id: string
|
|
53
|
+
title: string
|
|
54
|
+
type: BlockType
|
|
55
|
+
description: string
|
|
56
|
+
/** position relative to the parent container (service or module). */
|
|
57
|
+
position: { x: number; y: number }
|
|
58
|
+
status: BlockStatus
|
|
59
|
+
/** 0..1 implementation progress, derived from the running execution. */
|
|
60
|
+
progress: number
|
|
61
|
+
/** ids of tasks that must be implemented before this one (drives arrows). */
|
|
62
|
+
dependsOn: string[]
|
|
63
|
+
/** id of the ExecutionInstance currently running against this block, if any. */
|
|
64
|
+
executionId: string | null
|
|
65
|
+
/** granularity level; absent on legacy/persisted data means `frame`. */
|
|
66
|
+
level: BlockLevel
|
|
67
|
+
/** parent container: service or module for a task, service for a module. */
|
|
68
|
+
parentId: string | null
|
|
69
|
+
/** task-only: 0..1 confidence produced when the pipeline finishes. */
|
|
70
|
+
confidence?: number
|
|
71
|
+
/** task-only: auto-merge the PR when confidence ≥ this threshold (0..1). */
|
|
72
|
+
confidenceThreshold?: number
|
|
73
|
+
/** task-only: the module this task belongs to (created on implement if absent). */
|
|
74
|
+
moduleName?: string
|
|
75
|
+
/** task-only: the features this task implements (definition metadata). */
|
|
76
|
+
features?: string[]
|
|
77
|
+
/** ids of best-practice prompt fragments folded into this block's agent prompts. */
|
|
78
|
+
fragmentIds?: string[]
|
|
79
|
+
/** id of the model (from MODEL_CATALOG) to run this block's agents with; absent = default. */
|
|
80
|
+
modelId?: string
|
|
81
|
+
/** where this block's acceptance / Playwright tests run; absent = no preference. */
|
|
82
|
+
testTarget?: TestTarget
|
|
83
|
+
/** the PR the block's implementer agent opened for its work; absent = none yet. */
|
|
84
|
+
pullRequest?: PullRequestRef
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* A lightweight link from a block to the pull request its implementer agent
|
|
89
|
+
* opened. Just enough to display the PR on the board and navigate to it; mirrors
|
|
90
|
+
* `PullRequestRef` in `@cat-factory/contracts`.
|
|
91
|
+
*/
|
|
92
|
+
export interface PullRequestRef {
|
|
93
|
+
/** The PR's web URL, opened when the user clicks through from the board. */
|
|
94
|
+
url: string
|
|
95
|
+
/** The PR number within the repo, shown as `#<number>` when known. */
|
|
96
|
+
number?: number
|
|
97
|
+
/** The head branch the agent pushed its work to, when known. */
|
|
98
|
+
branch?: string
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Where a block's acceptance / Playwright tests run:
|
|
103
|
+
* - `github_actions` in the project's CI, against a service spun up in the run
|
|
104
|
+
* - `ephemeral_env` against the provisioned ephemeral environment for the run
|
|
105
|
+
*/
|
|
106
|
+
export type TestTarget = 'github_actions' | 'ephemeral_env'
|
|
107
|
+
|
|
108
|
+
/** The kinds of agents available in the agent palette. */
|
|
109
|
+
export type AgentKind =
|
|
110
|
+
| 'architect'
|
|
111
|
+
| 'researcher'
|
|
112
|
+
| 'coder'
|
|
113
|
+
| 'tester'
|
|
114
|
+
| 'reviewer'
|
|
115
|
+
| 'documenter'
|
|
116
|
+
| 'integrator'
|
|
117
|
+
| 'acceptance'
|
|
118
|
+
| 'playwright'
|
|
119
|
+
| 'mocker'
|
|
120
|
+
| 'business-documenter'
|
|
121
|
+
| 'business-reviewer'
|
|
122
|
+
|
|
123
|
+
/** A draggable agent definition shown in the agent palette. */
|
|
124
|
+
export interface AgentArchetype {
|
|
125
|
+
kind: AgentKind
|
|
126
|
+
label: string
|
|
127
|
+
/** iconify name (lucide) */
|
|
128
|
+
icon: string
|
|
129
|
+
/** tailwind-ish accent token used across chips / borders */
|
|
130
|
+
color: string
|
|
131
|
+
description: string
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/** A reusable, linear sequence of agents. */
|
|
135
|
+
export interface Pipeline {
|
|
136
|
+
id: string
|
|
137
|
+
name: string
|
|
138
|
+
/** ordered agent kinds — the chain executes left to right */
|
|
139
|
+
agentKinds: AgentKind[]
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Spend-safeguard status for the current billing period (a calendar month).
|
|
144
|
+
* Token usage is priced into a single currency and gated by a budget; once
|
|
145
|
+
* `exceeded`, runs are paused and the frontend shows a large warning.
|
|
146
|
+
*/
|
|
147
|
+
export interface SpendStatus {
|
|
148
|
+
/** Start of the current billing period (epoch ms). */
|
|
149
|
+
periodStart: number
|
|
150
|
+
inputTokens: number
|
|
151
|
+
outputTokens: number
|
|
152
|
+
/** Estimated spend this period, in `currency`. */
|
|
153
|
+
costSpent: number
|
|
154
|
+
/** Configured budget for one period, in `currency`. */
|
|
155
|
+
costLimit: number
|
|
156
|
+
/** ISO 4217 currency (e.g. 'EUR'). */
|
|
157
|
+
currency: string
|
|
158
|
+
/** True once the budget is reached: execution is paused. */
|
|
159
|
+
exceeded: boolean
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/** A board/project container owned by the backend. */
|
|
163
|
+
export interface Workspace {
|
|
164
|
+
id: string
|
|
165
|
+
name: string
|
|
166
|
+
createdAt: number
|
|
167
|
+
/** The account this board belongs to, or null for a legacy/unscoped board. */
|
|
168
|
+
accountId: string | null
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/** Full server-side state of a workspace, returned on load and after resets. */
|
|
172
|
+
export interface WorkspaceSnapshot {
|
|
173
|
+
workspace: Workspace
|
|
174
|
+
blocks: Block[]
|
|
175
|
+
pipelines: Pipeline[]
|
|
176
|
+
executions: ExecutionInstance[]
|
|
177
|
+
/** Bootstrap runs (the unified `agent_runs` bootstrap rows), so the board can
|
|
178
|
+
* render a bootstrap's live progress / failure + retry on load. Absent on
|
|
179
|
+
* older servers. */
|
|
180
|
+
bootstrapJobs?: BootstrapJob[]
|
|
181
|
+
/** Current spend-safeguard status; absent on older servers. */
|
|
182
|
+
spend?: SpendStatus
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Real-time events pushed over the workspace WebSocket stream (see
|
|
187
|
+
* `useWorkspaceStream`). Mirrors `WorkspaceEvent` in `@cat-factory/contracts`.
|
|
188
|
+
*/
|
|
189
|
+
export type WorkspaceEvent =
|
|
190
|
+
| { type: 'execution'; instance: ExecutionInstance; block: Block | null; at: number }
|
|
191
|
+
| { type: 'board'; reason: string; at: number }
|
|
192
|
+
| { type: 'bootstrap'; job: BootstrapJob; block: Block | null; at: number }
|
|
193
|
+
|
|
194
|
+
/** Level-of-detail buckets driven by the canvas zoom level. */
|
|
195
|
+
export type LodLevel = 'far' | 'mid' | 'close'
|
|
196
|
+
|
|
197
|
+
/** The signed-in GitHub user, as returned by the backend's /auth/me. */
|
|
198
|
+
export interface AuthUser {
|
|
199
|
+
/** GitHub user id (stable across renames). */
|
|
200
|
+
id: number
|
|
201
|
+
login: string
|
|
202
|
+
name: string | null
|
|
203
|
+
avatarUrl: string | null
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Re-export the adjacent domain modules so `~/types/domain` remains the single
|
|
207
|
+
// import surface for the whole frontend.
|
|
208
|
+
export type * from './execution'
|
|
209
|
+
export type * from './models'
|
|
210
|
+
export type * from './fragments'
|
|
211
|
+
export type * from './documents'
|
|
212
|
+
export type * from './tasks'
|
|
213
|
+
export type * from './scenarios'
|
|
214
|
+
export type * from './bootstrap'
|
|
215
|
+
export type * from './github'
|
|
216
|
+
export type * from './accounts'
|