@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,110 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// Execution model: a pipeline of agents running against a single block.
|
|
3
|
+
// Mirrors the `@cat-factory/contracts` execution schemas.
|
|
4
|
+
// ---------------------------------------------------------------------------
|
|
5
|
+
|
|
6
|
+
import type { AgentKind } from './domain'
|
|
7
|
+
|
|
8
|
+
/** Runtime state of a single agent within a running execution. */
|
|
9
|
+
export type AgentState =
|
|
10
|
+
| 'pending' // not started
|
|
11
|
+
| 'working' // actively (visually) working
|
|
12
|
+
| 'waiting_decision' // paused, needs a human decision
|
|
13
|
+
| 'done' // finished
|
|
14
|
+
|
|
15
|
+
/** A decision an agent surfaces mid-step that a human must resolve. */
|
|
16
|
+
export interface Decision {
|
|
17
|
+
id: string
|
|
18
|
+
question: string
|
|
19
|
+
options: string[]
|
|
20
|
+
chosen: string | null
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** One entry of a running step's todo list — its label and current status. */
|
|
24
|
+
export interface StepSubtaskItem {
|
|
25
|
+
label: string
|
|
26
|
+
status: 'pending' | 'in_progress' | 'completed'
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Live subtask counts a running step reports from the agent's own todo list. */
|
|
30
|
+
export interface StepSubtasks {
|
|
31
|
+
completed: number
|
|
32
|
+
inProgress: number
|
|
33
|
+
total: number
|
|
34
|
+
/** The individual todo entries, so a zoomed-in card can show the actual list. */
|
|
35
|
+
items?: StepSubtaskItem[]
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// ---------------------------------------------------------------------------
|
|
39
|
+
// Shared "agent run" failure model. Both flows that run an agent in a container
|
|
40
|
+
// — a task pipeline `execution` and a repo `bootstrap` — surface failures with
|
|
41
|
+
// this shape, so the board renders one failure banner + retry for either. Mirrors
|
|
42
|
+
// `agentFailureSchema` in `@cat-factory/contracts`.
|
|
43
|
+
// ---------------------------------------------------------------------------
|
|
44
|
+
|
|
45
|
+
/** The agent flows that produce a container-backed "agent run". */
|
|
46
|
+
export type AgentRunKind = 'bootstrap' | 'execution'
|
|
47
|
+
|
|
48
|
+
/** How an agent run faulted, so the board can classify it (and hint at a retry).
|
|
49
|
+
* The union spans both flows; a given flow only ever produces a subset. */
|
|
50
|
+
export type AgentFailureKind =
|
|
51
|
+
| 'preflight'
|
|
52
|
+
| 'dispatch'
|
|
53
|
+
| 'evicted'
|
|
54
|
+
| 'timeout'
|
|
55
|
+
| 'agent'
|
|
56
|
+
| 'job_failed'
|
|
57
|
+
| 'decision_timeout'
|
|
58
|
+
| 'unknown'
|
|
59
|
+
|
|
60
|
+
/** Structured diagnostics captured when an agent run fails. */
|
|
61
|
+
export interface AgentFailure {
|
|
62
|
+
kind: AgentFailureKind
|
|
63
|
+
/** Human-readable summary (mirrors the run's one-line `error`). */
|
|
64
|
+
message: string
|
|
65
|
+
/** Extended detail when available (the harness's reason, an HTTP body, …). */
|
|
66
|
+
detail: string | null
|
|
67
|
+
/** Where to look next (e.g. "check the container logs for this job id"). */
|
|
68
|
+
hint: string | null
|
|
69
|
+
/** Epoch ms the failure was recorded. */
|
|
70
|
+
occurredAt: number
|
|
71
|
+
/** Last subtask counts seen before the failure, for context (null if none). */
|
|
72
|
+
lastSubtasks: StepSubtasks | null
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** One agent's slot in a running pipeline. */
|
|
76
|
+
export interface PipelineStep {
|
|
77
|
+
agentKind: AgentKind
|
|
78
|
+
state: AgentState
|
|
79
|
+
/** 0..1 progress of this individual step */
|
|
80
|
+
progress: number
|
|
81
|
+
/** live "N/M done" subtask counts while an async (container) step runs */
|
|
82
|
+
subtasks?: StepSubtasks
|
|
83
|
+
/** present + unresolved => the step (and block) is blocked */
|
|
84
|
+
decision: Decision | null
|
|
85
|
+
/** text the agent produced for this step (when LLM execution is enabled). */
|
|
86
|
+
output?: string
|
|
87
|
+
/** identifier of the model that produced `output`, for transparency. */
|
|
88
|
+
model?: string
|
|
89
|
+
/** prompt-fragment library ids folded into this step (manual ∪ selector pick). */
|
|
90
|
+
selectedFragmentIds?: string[]
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** A pipeline instance running against one block. */
|
|
94
|
+
export interface ExecutionInstance {
|
|
95
|
+
id: string
|
|
96
|
+
blockId: string
|
|
97
|
+
pipelineId: string
|
|
98
|
+
pipelineName: string
|
|
99
|
+
steps: PipelineStep[]
|
|
100
|
+
/** index into steps of the currently active step */
|
|
101
|
+
currentStep: number
|
|
102
|
+
/**
|
|
103
|
+
* 'paused' = halted by the spend safeguard until the budget frees up;
|
|
104
|
+
* 'failed' = the run faulted (see `failure`) — surfaces the shared failure
|
|
105
|
+
* banner + retry, instead of the old success-looking `pr_ready` lie.
|
|
106
|
+
*/
|
|
107
|
+
status: 'running' | 'blocked' | 'done' | 'paused' | 'failed'
|
|
108
|
+
/** Structured failure diagnostics when `status` is `failed`; null otherwise. */
|
|
109
|
+
failure?: AgentFailure | null
|
|
110
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// Prompt-fragment library (ADR 0006). Mirrors the `@cat-factory/contracts`
|
|
3
|
+
// fragment-library schemas: the managed, tenant-scoped catalog a workspace
|
|
4
|
+
// resolves (built-in ∪ account ∪ workspace), plus the repo sources that feed it.
|
|
5
|
+
// ---------------------------------------------------------------------------
|
|
6
|
+
|
|
7
|
+
import type { AgentKind, BlockType } from './domain'
|
|
8
|
+
import type { PromptFragment } from './models'
|
|
9
|
+
|
|
10
|
+
/** Which scope owns a managed fragment / source. */
|
|
11
|
+
export type FragmentOwnerKind = 'account' | 'workspace'
|
|
12
|
+
|
|
13
|
+
/** The tier a resolved fragment originates from after override-by-id. */
|
|
14
|
+
export type FragmentTier = 'builtin' | 'account' | 'workspace'
|
|
15
|
+
|
|
16
|
+
/** Inputs for creating a hand-authored fragment at a tier. */
|
|
17
|
+
export interface CreatePromptFragmentInput {
|
|
18
|
+
id?: string
|
|
19
|
+
title: string
|
|
20
|
+
category?: string
|
|
21
|
+
summary: string
|
|
22
|
+
body: string
|
|
23
|
+
tags?: string[]
|
|
24
|
+
appliesTo?: { blockTypes?: BlockType[]; agentKinds?: AgentKind[] }
|
|
25
|
+
version?: string
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** Partial patch for editing a fragment at a tier. */
|
|
29
|
+
export type UpdatePromptFragmentInput = Partial<CreatePromptFragmentInput>
|
|
30
|
+
|
|
31
|
+
/** A fragment after the three tiers are merged for a workspace. */
|
|
32
|
+
export interface ResolvedFragment extends PromptFragment {
|
|
33
|
+
tier: FragmentTier
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** A repo directory linked as a source of Markdown guideline files. */
|
|
37
|
+
export interface FragmentSource {
|
|
38
|
+
id: string
|
|
39
|
+
ownerKind: FragmentOwnerKind
|
|
40
|
+
ownerId: string
|
|
41
|
+
repoOwner: string
|
|
42
|
+
repoName: string
|
|
43
|
+
gitRef: string
|
|
44
|
+
dirPath: string
|
|
45
|
+
lastSyncedSha: string | null
|
|
46
|
+
lastSyncedAt: number | null
|
|
47
|
+
createdAt: number
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Inputs for linking a repo directory as a fragment source. */
|
|
51
|
+
export interface LinkFragmentSourceInput {
|
|
52
|
+
repoOwner: string
|
|
53
|
+
repoName: string
|
|
54
|
+
gitRef?: string
|
|
55
|
+
dirPath?: string
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** Outcome of resyncing a source. */
|
|
59
|
+
export interface FragmentSyncResult {
|
|
60
|
+
upserted: number
|
|
61
|
+
tombstoned: number
|
|
62
|
+
unchanged: number
|
|
63
|
+
lastSyncedSha: string | null
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** Cheap "check for changes" result (no writes); powers the resync badge. */
|
|
67
|
+
export interface FragmentSourceStatus {
|
|
68
|
+
changed: boolean
|
|
69
|
+
changedCount: number
|
|
70
|
+
lastSyncedSha: string | null
|
|
71
|
+
remoteSha: string | null
|
|
72
|
+
}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// GitHub integration. The backend's `GitHubModule` projects GitHub data
|
|
3
|
+
// (repos/branches, pull requests/issues) into D1 and serves it fast and
|
|
4
|
+
// rate-limit-free, alongside connect/resync/write endpoints. These mirror the
|
|
5
|
+
// `@cat-factory/contracts` GitHub schemas so responses drop straight into the
|
|
6
|
+
// Pinia store, just as the document-source types do for Confluence/Notion.
|
|
7
|
+
// ---------------------------------------------------------------------------
|
|
8
|
+
|
|
9
|
+
/** A workspace's GitHub App installation, as exposed to clients (no token). */
|
|
10
|
+
export interface GitHubConnection {
|
|
11
|
+
installationId: number
|
|
12
|
+
accountLogin: string
|
|
13
|
+
targetType: 'Organization' | 'User'
|
|
14
|
+
connectedAt: number
|
|
15
|
+
/**
|
|
16
|
+
* Whether cat-factory can create repos under this account itself (privileged
|
|
17
|
+
* App tier). When true, the bootstrap UI drops the manual "create on GitHub"
|
|
18
|
+
* step. Defaults to false for older backends that don't send it.
|
|
19
|
+
*/
|
|
20
|
+
canCreateRepos?: boolean
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* A discoverable App installation for the connect picker. `connected` says
|
|
25
|
+
* whether it's already bound: to THIS workspace, to ANOTHER (so connecting would
|
|
26
|
+
* be rejected), or to NONE (free to connect).
|
|
27
|
+
*/
|
|
28
|
+
export interface GitHubInstallationOption {
|
|
29
|
+
installationId: number
|
|
30
|
+
accountLogin: string
|
|
31
|
+
targetType: 'Organization' | 'User'
|
|
32
|
+
accountAvatarUrl: string | null
|
|
33
|
+
connected: 'this' | 'other' | 'none'
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** A repository the integration tracks for a workspace. */
|
|
37
|
+
export interface GitHubRepo {
|
|
38
|
+
githubId: number
|
|
39
|
+
installationId: number
|
|
40
|
+
owner: string
|
|
41
|
+
name: string
|
|
42
|
+
defaultBranch: string | null
|
|
43
|
+
private: boolean
|
|
44
|
+
/** Optional link to a board block this repo backs. */
|
|
45
|
+
blockId: string | null
|
|
46
|
+
syncedAt: number
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export interface GitHubBranch {
|
|
50
|
+
repoGithubId: number
|
|
51
|
+
name: string
|
|
52
|
+
headSha: string
|
|
53
|
+
protected: boolean
|
|
54
|
+
syncedAt: number
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* A repo the connected installation can access, annotated with whether the
|
|
59
|
+
* current workspace links it. Drives the per-workspace repo picker — repos are
|
|
60
|
+
* linked explicitly per board, since the installation is shared across an
|
|
61
|
+
* account's workspaces.
|
|
62
|
+
*/
|
|
63
|
+
export interface GitHubAvailableRepo {
|
|
64
|
+
githubId: number
|
|
65
|
+
owner: string
|
|
66
|
+
name: string
|
|
67
|
+
defaultBranch: string | null
|
|
68
|
+
private: boolean
|
|
69
|
+
linked: boolean
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export type GitHubPullRequestState = 'open' | 'closed'
|
|
73
|
+
|
|
74
|
+
export interface GitHubPullRequest {
|
|
75
|
+
repoGithubId: number
|
|
76
|
+
number: number
|
|
77
|
+
githubId: number
|
|
78
|
+
title: string
|
|
79
|
+
state: GitHubPullRequestState
|
|
80
|
+
headRef: string | null
|
|
81
|
+
baseRef: string | null
|
|
82
|
+
headSha: string | null
|
|
83
|
+
merged: boolean
|
|
84
|
+
author: string | null
|
|
85
|
+
updatedAt: number | null
|
|
86
|
+
syncedAt: number
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export type GitHubIssueState = 'open' | 'closed'
|
|
90
|
+
|
|
91
|
+
export interface GitHubIssue {
|
|
92
|
+
repoGithubId: number
|
|
93
|
+
number: number
|
|
94
|
+
githubId: number
|
|
95
|
+
title: string
|
|
96
|
+
state: GitHubIssueState
|
|
97
|
+
author: string | null
|
|
98
|
+
labels: string[]
|
|
99
|
+
updatedAt: number | null
|
|
100
|
+
syncedAt: number
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// ---- request inputs -------------------------------------------------------
|
|
104
|
+
|
|
105
|
+
/** Trigger a resync. Defaults to an incremental resync of all tracked repos. */
|
|
106
|
+
export interface ResyncRequest {
|
|
107
|
+
/** Limit the resync to a single repo (by its GitHub numeric id). */
|
|
108
|
+
repoGithubId?: number
|
|
109
|
+
/** Run a full backfill (durable Workflow) instead of an incremental pass. */
|
|
110
|
+
full?: boolean
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export interface CreateBranchInput {
|
|
114
|
+
name: string
|
|
115
|
+
fromSha: string
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** Body for programmatic repo creation (privileged App tier). */
|
|
119
|
+
export interface CreateRepoRequest {
|
|
120
|
+
/** A single GitHub name segment — no "owner/" prefix. */
|
|
121
|
+
name: string
|
|
122
|
+
private?: boolean
|
|
123
|
+
description?: string
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/** The freshly-created repository returned by the create-repo endpoint. */
|
|
127
|
+
export interface CreatedRepo {
|
|
128
|
+
githubId: number
|
|
129
|
+
owner: string
|
|
130
|
+
name: string
|
|
131
|
+
defaultBranch: string | null
|
|
132
|
+
private: boolean
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export interface CommitFilesInput {
|
|
136
|
+
branch: string
|
|
137
|
+
message: string
|
|
138
|
+
files: { path: string; content: string }[]
|
|
139
|
+
/** Parent commit to build on; defaults to the branch tip. */
|
|
140
|
+
baseSha?: string
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export interface OpenPullRequestInput {
|
|
144
|
+
title: string
|
|
145
|
+
head: string
|
|
146
|
+
base: string
|
|
147
|
+
body?: string
|
|
148
|
+
draft?: boolean
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export interface MergePullRequestInput {
|
|
152
|
+
method?: 'merge' | 'squash' | 'rebase'
|
|
153
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// Model selection & best-practice prompt fragments. Mirrors the
|
|
3
|
+
// `@cat-factory/contracts` schemas served read-only by the backend.
|
|
4
|
+
// ---------------------------------------------------------------------------
|
|
5
|
+
|
|
6
|
+
import type { AgentKind, BlockType } from './domain'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* A selectable LLM model, resolved to the flavour actually in use for this
|
|
10
|
+
* deployment (served by `GET /models`). `flavor` is `direct` when the model's
|
|
11
|
+
* own provider key is configured, else `cloudflare`. Mirrors `ModelOption` in
|
|
12
|
+
* `@cat-factory/contracts`.
|
|
13
|
+
*/
|
|
14
|
+
export interface ModelOption {
|
|
15
|
+
id: string
|
|
16
|
+
label: string
|
|
17
|
+
description: string
|
|
18
|
+
flavor: 'cloudflare' | 'direct'
|
|
19
|
+
providerLabel: string
|
|
20
|
+
provider: string
|
|
21
|
+
model: string
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* A curated best-practice "prompt fragment" served read-only by the backend
|
|
26
|
+
* (`GET /prompt-fragments`). Users pick which apply to a block; the backend folds
|
|
27
|
+
* the selected fragments' bodies into the agent system prompt at run time.
|
|
28
|
+
*/
|
|
29
|
+
export interface PromptFragment {
|
|
30
|
+
id: string
|
|
31
|
+
version: string
|
|
32
|
+
title: string
|
|
33
|
+
category: string
|
|
34
|
+
summary: string
|
|
35
|
+
body: string
|
|
36
|
+
appliesTo?: {
|
|
37
|
+
blockTypes?: BlockType[]
|
|
38
|
+
agentKinds?: AgentKind[]
|
|
39
|
+
}
|
|
40
|
+
/** Free-form tags the relevance selector uses (managed/sourced fragments only). */
|
|
41
|
+
tags?: string[]
|
|
42
|
+
/** Provenance when sourced from a repo; absent for built-in/hand-authored. */
|
|
43
|
+
source?: {
|
|
44
|
+
sourceId: string
|
|
45
|
+
path: string
|
|
46
|
+
sha: string
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// Requirements-review wire types. Mirror of `@cat-factory/contracts`'
|
|
2
|
+
// requirements.ts, kept in sync by hand like the rest of `~/types/*` (the SPA
|
|
3
|
+
// does not import the backend package directly).
|
|
4
|
+
//
|
|
5
|
+
// A stateless reviewer agent inspects a block's collected requirements and
|
|
6
|
+
// raises questions / gaps / clarifications; a human answers or dismisses each;
|
|
7
|
+
// then the agent folds the answers back into the block's requirements.
|
|
8
|
+
|
|
9
|
+
export type ReviewItemCategory = 'gap' | 'clarification' | 'assumption' | 'risk' | 'question'
|
|
10
|
+
|
|
11
|
+
export type ReviewItemSeverity = 'low' | 'medium' | 'high'
|
|
12
|
+
|
|
13
|
+
export type ReviewItemStatus = 'open' | 'answered' | 'resolved' | 'dismissed'
|
|
14
|
+
|
|
15
|
+
export interface RequirementReviewItem {
|
|
16
|
+
id: string
|
|
17
|
+
category: ReviewItemCategory
|
|
18
|
+
severity: ReviewItemSeverity
|
|
19
|
+
title: string
|
|
20
|
+
detail: string
|
|
21
|
+
status: ReviewItemStatus
|
|
22
|
+
reply: string | null
|
|
23
|
+
createdAt: number
|
|
24
|
+
updatedAt: number
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export type RequirementReviewStatus = 'ready' | 'incorporated'
|
|
28
|
+
|
|
29
|
+
export interface RequirementReview {
|
|
30
|
+
id: string
|
|
31
|
+
blockId: string
|
|
32
|
+
status: RequirementReviewStatus
|
|
33
|
+
items: RequirementReviewItem[]
|
|
34
|
+
model: string | null
|
|
35
|
+
incorporatedRequirements: string | null
|
|
36
|
+
createdAt: number
|
|
37
|
+
updatedAt: number
|
|
38
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// Acceptance test scenarios.
|
|
3
|
+
//
|
|
4
|
+
// A feature-scoped, black-box acceptance scenario in Given / When / Then form.
|
|
5
|
+
// These are what the `acceptance` agent produces from a block's requirements
|
|
6
|
+
// (description + linked PRDs), and what the `playwright` agent turns into
|
|
7
|
+
// end-to-end tests. The board is server-owned, but — like the agent palette —
|
|
8
|
+
// scenarios are an editable, client-side prototype surface, persisted locally so
|
|
9
|
+
// a user can author and refine the set for a feature before wiring up real runs.
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
|
|
12
|
+
/** Where a scenario came from: drafted by the acceptance agent or hand-written. */
|
|
13
|
+
export type ScenarioSource = 'generated' | 'manual'
|
|
14
|
+
|
|
15
|
+
/** Review state of a scenario in its feature's current set. */
|
|
16
|
+
export type ScenarioStatus = 'draft' | 'approved'
|
|
17
|
+
|
|
18
|
+
/** A single acceptance scenario for a feature. */
|
|
19
|
+
export interface AcceptanceScenario {
|
|
20
|
+
id: string
|
|
21
|
+
/** The feature (matching `Block.features`) this scenario verifies. */
|
|
22
|
+
feature: string
|
|
23
|
+
/** Short, human title — also the name of the generated Playwright test. */
|
|
24
|
+
title: string
|
|
25
|
+
/** Preconditions (one clause per entry). */
|
|
26
|
+
given: string[]
|
|
27
|
+
/** The user action under test (kept to a single clear action). */
|
|
28
|
+
when: string[]
|
|
29
|
+
/** Observable, asserted outcomes. */
|
|
30
|
+
then: string[]
|
|
31
|
+
status: ScenarioStatus
|
|
32
|
+
source: ScenarioSource
|
|
33
|
+
/** True once a Playwright test has been generated for this scenario. */
|
|
34
|
+
hasPlaywrightTest: boolean
|
|
35
|
+
createdAt: number
|
|
36
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// Task-source integration. Individual issues imported from external task
|
|
3
|
+
// trackers (Jira, …) can be attached to a board task as agent context. These
|
|
4
|
+
// mirror the `@cat-factory/contracts` task schemas; the abstraction is
|
|
5
|
+
// source-agnostic, keyed by `source`. Unlike document sources there is no
|
|
6
|
+
// plan/spawn — an issue is linked for context, never expanded into structure.
|
|
7
|
+
// ---------------------------------------------------------------------------
|
|
8
|
+
|
|
9
|
+
import type { CredentialField } from './documents'
|
|
10
|
+
|
|
11
|
+
/** The external task trackers cat-factory can link to. */
|
|
12
|
+
export type TaskSourceKind = 'jira'
|
|
13
|
+
|
|
14
|
+
export type { CredentialField }
|
|
15
|
+
|
|
16
|
+
/** A source's self-description: drives the generic connect + import UI. */
|
|
17
|
+
export interface TaskSourceDescriptor {
|
|
18
|
+
source: TaskSourceKind
|
|
19
|
+
label: string
|
|
20
|
+
/** Lucide icon name for the source. */
|
|
21
|
+
icon: string
|
|
22
|
+
credentialFields: CredentialField[]
|
|
23
|
+
refLabel: string
|
|
24
|
+
refPlaceholder: string
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** A workspace's connection to a task source (never carries credentials). */
|
|
28
|
+
export interface TaskConnection {
|
|
29
|
+
source: TaskSourceKind
|
|
30
|
+
/** Human-friendly label for what we're connected to (site URL). */
|
|
31
|
+
label: string
|
|
32
|
+
/** When the connection was established (epoch ms). */
|
|
33
|
+
connectedAt: number
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** A single comment on an issue, with its body as lightweight Markdown. */
|
|
37
|
+
export interface TaskComment {
|
|
38
|
+
author: string
|
|
39
|
+
createdAt: string
|
|
40
|
+
body: string
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** An issue imported from a source into the workspace, as a structured record. */
|
|
44
|
+
export interface SourceTask {
|
|
45
|
+
source: TaskSourceKind
|
|
46
|
+
/** The source's canonical key for the issue (e.g. `PROJ-123`). */
|
|
47
|
+
externalId: string
|
|
48
|
+
title: string
|
|
49
|
+
url: string
|
|
50
|
+
/** Workflow status name, e.g. `In Progress`. */
|
|
51
|
+
status: string
|
|
52
|
+
/** Issue type name, e.g. `Bug`. */
|
|
53
|
+
type: string
|
|
54
|
+
/** Assignee display name, or null when unassigned. */
|
|
55
|
+
assignee: string | null
|
|
56
|
+
/** Priority name, or null when none. */
|
|
57
|
+
priority: string | null
|
|
58
|
+
labels: string[]
|
|
59
|
+
/** Issue description as lightweight Markdown. */
|
|
60
|
+
description: string
|
|
61
|
+
comments: TaskComment[]
|
|
62
|
+
/** Short plain-text preview of the issue. */
|
|
63
|
+
excerpt: string
|
|
64
|
+
/** The board block this issue is attached to as context, if any. */
|
|
65
|
+
linkedBlockId: string | null
|
|
66
|
+
syncedAt: number
|
|
67
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest'
|
|
2
|
+
import type { AgentKind, BlockStatus, BlockType } from '~/types/domain'
|
|
3
|
+
import {
|
|
4
|
+
AGENT_ARCHETYPES,
|
|
5
|
+
AGENT_BY_KIND,
|
|
6
|
+
BLOCK_TYPE_META,
|
|
7
|
+
STATUS_META,
|
|
8
|
+
DEFAULT_CONFIDENCE_THRESHOLD,
|
|
9
|
+
uid,
|
|
10
|
+
} from '~/utils/catalog'
|
|
11
|
+
|
|
12
|
+
const AGENT_KINDS: AgentKind[] = [
|
|
13
|
+
'architect',
|
|
14
|
+
'researcher',
|
|
15
|
+
'coder',
|
|
16
|
+
'tester',
|
|
17
|
+
'reviewer',
|
|
18
|
+
'documenter',
|
|
19
|
+
'integrator',
|
|
20
|
+
'acceptance',
|
|
21
|
+
'playwright',
|
|
22
|
+
'mocker',
|
|
23
|
+
'business-documenter',
|
|
24
|
+
'business-reviewer',
|
|
25
|
+
]
|
|
26
|
+
const BLOCK_TYPES: BlockType[] = [
|
|
27
|
+
'frontend',
|
|
28
|
+
'service',
|
|
29
|
+
'api',
|
|
30
|
+
'database',
|
|
31
|
+
'queue',
|
|
32
|
+
'integration',
|
|
33
|
+
'external',
|
|
34
|
+
]
|
|
35
|
+
const BLOCK_STATUSES: BlockStatus[] = [
|
|
36
|
+
'planned',
|
|
37
|
+
'ready',
|
|
38
|
+
'in_progress',
|
|
39
|
+
'blocked',
|
|
40
|
+
'pr_ready',
|
|
41
|
+
'done',
|
|
42
|
+
]
|
|
43
|
+
|
|
44
|
+
describe('catalog', () => {
|
|
45
|
+
it('indexes every archetype by its kind', () => {
|
|
46
|
+
expect(Object.keys(AGENT_BY_KIND).sort()).toEqual([...AGENT_KINDS].sort())
|
|
47
|
+
for (const a of AGENT_ARCHETYPES) {
|
|
48
|
+
expect(AGENT_BY_KIND[a.kind]).toBe(a)
|
|
49
|
+
}
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
it('provides metadata for every block type', () => {
|
|
53
|
+
for (const t of BLOCK_TYPES) {
|
|
54
|
+
expect(BLOCK_TYPE_META[t]).toMatchObject({
|
|
55
|
+
label: expect.any(String),
|
|
56
|
+
icon: expect.any(String),
|
|
57
|
+
accent: expect.any(String),
|
|
58
|
+
})
|
|
59
|
+
}
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
it('provides metadata for every block status', () => {
|
|
63
|
+
for (const s of BLOCK_STATUSES) {
|
|
64
|
+
expect(STATUS_META[s]).toMatchObject({
|
|
65
|
+
label: expect.any(String),
|
|
66
|
+
color: expect.any(String),
|
|
67
|
+
chip: expect.any(String),
|
|
68
|
+
icon: expect.any(String),
|
|
69
|
+
})
|
|
70
|
+
}
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
it('keeps the default confidence threshold within 0..1', () => {
|
|
74
|
+
expect(DEFAULT_CONFIDENCE_THRESHOLD).toBeGreaterThan(0)
|
|
75
|
+
expect(DEFAULT_CONFIDENCE_THRESHOLD).toBeLessThanOrEqual(1)
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
it('uid produces prefixed, unique-ish ids', () => {
|
|
79
|
+
expect(uid('blk')).toMatch(/^blk_[a-z0-9]+$/)
|
|
80
|
+
expect(uid('blk')).not.toBe(uid('blk'))
|
|
81
|
+
})
|
|
82
|
+
})
|