-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathconfig-schema.json
More file actions
303 lines (303 loc) · 15.7 KB
/
Copy pathconfig-schema.json
File metadata and controls
303 lines (303 loc) · 15.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://github.com/ashbrener/spec-kit-linear/specs/001-spec-kit-linear-bridge/contracts/config-schema.json",
"title": "spec-kit-linear per-consumer-repo config",
"description": "Schema for .specify/extensions/linear/linear-config.yml. Mirrors config-template.yml. Locks the per-repo Linear binding (Team UUID + Project UUID + workflow_state_uuids map) plus operator-tweakable sync toggles, webhook installation status, and local git-hook installation status. Loaded by src/config.sh at the start of every reconcile; validation failures abort the run with exit code 2 per FR-022.",
"type": "object",
"additionalProperties": false,
"required": [
"schema_version",
"config_version",
"linear",
"sync",
"webhook",
"git_hooks"
],
"properties": {
"schema_version": {
"type": "integer",
"description": "Template schema version. Bumped when the shape of this file changes (breaking).",
"const": 1
},
"config_version": {
"type": "integer",
"description": "Operator-edit counter. Bumped by hand if the operator hand-edits any field; informational only, not enforced.",
"minimum": 1
},
"linear": {
"type": "object",
"description": "The Linear binding. All UUIDs MUST be present and well-formed before src/reconcile.sh issues any mutation (FR-002, FR-032, Principle V).",
"additionalProperties": false,
"required": [
"workspace",
"team",
"project",
"workflow_state_uuids",
"default_state_uuids"
],
"properties": {
"workspace": {
"type": "object",
"description": "Workspace identity. Informational only; the OAuth token / API key implicitly scopes to one workspace.",
"additionalProperties": false,
"required": ["name", "url_key"],
"properties": {
"name": {
"type": "string",
"description": "Human-readable workspace name as shown in the Linear UI.",
"minLength": 1
},
"url_key": {
"type": "string",
"description": "Workspace URL slug (the subdomain segment in linear.app/<slug>/).",
"pattern": "^[a-z0-9-]+$",
"minLength": 1
}
}
},
"team": {
"type": "object",
"description": "Owning Linear Team. The bridge writes every spec Issue and task-phase sub-issue into this Team (FR-002).",
"additionalProperties": false,
"required": ["id"],
"properties": {
"id": {
"type": "string",
"description": "Team UUID. Authoritative lookup key — name changes in Linear UI MUST NOT break the bridge.",
"pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$"
},
"key": {
"type": "string",
"description": "Linear team key (e.g. 'ACM'). Informational; surfaced in Linear identifiers like ACM-123. Not used for lookup.",
"minLength": 1
},
"name": {
"type": "string",
"description": "Team display name. Informational.",
"minLength": 1
}
}
},
"project": {
"type": "object",
"description": "The Linear Project that represents this consumer repo. Exactly one Project per repo (FR-002).",
"additionalProperties": false,
"required": ["id"],
"properties": {
"id": {
"type": "string",
"description": "Project UUID. Authoritative lookup key. Resolved at install time; never inferred at reconcile time.",
"pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$"
},
"name": {
"type": "string",
"description": "Project display name. Informational; may drift from Linear UI without breaking sync.",
"minLength": 1
}
}
},
"operator": {
"type": "object",
"description": "Operator identity, resolved at install time via the GraphQL `viewer { id name email }` query (FR-034). user_id is the lookup key; name/email are informational. The reconciler passes user_id as assigneeId on every issueCreate mutation (spec Issues and task-phase sub-issues); issueUpdate never passes assigneeId so manual reassignment in Linear's UI persists across reconciles. Absent operator block → reconciler warns and creates Issues unassigned (graceful degradation per FR-034).",
"additionalProperties": false,
"required": ["user_id"],
"properties": {
"user_id": {
"type": "string",
"description": "Operator's Linear user UUID. Resolved at install time; used as assigneeId on every issueCreate.",
"pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$"
},
"name": {
"type": "string",
"description": "Operator display name. Informational; surfaced in install summary + spec Issue memory block.",
"minLength": 1
},
"email": {
"type": "string",
"format": "email",
"description": "Operator email. Informational; surfaced in install summary + spec Issue memory block."
}
}
},
"workflow_state_uuids": {
"type": "object",
"description": "Map of lifecycle-phase key to Linear workflow-state UUID, captured by speckit.linear.seed at workspace-seed time (FR-021, FR-032). All nine keys MUST be present and well-formed UUIDs. Lookup is by UUID, never by name — Linear UI renames are safe; deletion of a referenced state surfaces as an explicit error at reconcile time.",
"additionalProperties": false,
"required": [
"specifying",
"clarifying",
"planning",
"tasking",
"red_team",
"implementing",
"analyzing",
"ready_to_merge",
"merged"
],
"properties": {
"specifying": {
"type": "string",
"description": "Workflow-state UUID for the 'Specifying' lifecycle phase (state-type: unstarted).",
"pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$"
},
"clarifying": {
"type": "string",
"description": "Workflow-state UUID for the 'Clarifying' phase (state-type: unstarted).",
"pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$"
},
"planning": {
"type": "string",
"description": "Workflow-state UUID for the 'Planning' phase (state-type: unstarted).",
"pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$"
},
"tasking": {
"type": "string",
"description": "Workflow-state UUID for the 'Tasking' phase (state-type: unstarted).",
"pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$"
},
"red_team": {
"type": "string",
"description": "Workflow-state UUID for the 'Red-team' phase (state-type: unstarted). Mirrors the optional speckit-red-team extension's gate phase.",
"pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$"
},
"implementing": {
"type": "string",
"description": "Workflow-state UUID for the 'Implementing' phase (state-type: started).",
"pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$"
},
"analyzing": {
"type": "string",
"description": "Workflow-state UUID for the 'Analyzing' phase (state-type: started).",
"pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$"
},
"ready_to_merge": {
"type": "string",
"description": "Workflow-state UUID for the 'Ready-to-merge' phase (state-type: started). Flipped by Layer E (GitHub Action) on PR opened / ready_for_review per FR-028.",
"pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$"
},
"merged": {
"type": "string",
"description": "Workflow-state UUID for the 'Merged' phase (state-type: completed). Flipped by Layer E on PR closed with merged=true per FR-028.",
"pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$"
}
}
},
"default_state_uuids": {
"type": "object",
"description": "Map of task-phase sub-issue state key to Linear workflow-state UUID, captured by speckit.linear.seed at workspace-seed time (FR-021, FR-032). Distinct from workflow_state_uuids: those drive the parent spec Issue's lifecycle progression (specifying → … → merged), while these drive the simpler todo/in_progress/done tri-state on each task-row sub-issue created by /speckit-implement. All three keys MUST be present and well-formed UUIDs. Lookup is by UUID, never by name — Linear UI renames are safe; deletion of a referenced state surfaces as an explicit error at reconcile time.",
"additionalProperties": false,
"required": [
"todo",
"in_progress",
"done"
],
"properties": {
"todo": {
"type": "string",
"description": "Workflow-state UUID for a newly-created task sub-issue that has not yet started (state-type: unstarted).",
"pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$"
},
"in_progress": {
"type": "string",
"description": "Workflow-state UUID for a task sub-issue currently in flight (state-type: started).",
"pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$"
},
"done": {
"type": "string",
"description": "Workflow-state UUID for a task sub-issue whose tasks.md row has been ticked complete (state-type: completed).",
"pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$"
}
}
},
"agent_label_uuids": {
"type": "object",
"description": "Map of AI-agent family name to Linear workspace-label UUID, captured by speckit.linear.seed at workspace-seed time (FR-021, FR-036). Drives the sticky `agent:<family>` label the reconciler stamps onto every Issue and sub-issue it touches, identifying which AI agent ran the reconcile. v1 ships UUIDs for the two canonical families (`claude`, `codex`); non-canonical agents resolved from `AGENT_NAME` at sync time (e.g. `gemini`, `cursor`) mint their labels lazily via the standard issueLabelCreate path and do NOT require an entry here. Block presence is required once seed has run post-FR-036; absence is allowed for backwards compatibility with older configs (reconciler degrades gracefully — no agent stamp, no halt). Lookup is by UUID, never by name — Linear UI renames are safe; deletion of a referenced label surfaces as an explicit error at reconcile time.",
"additionalProperties": false,
"properties": {
"claude": {
"type": "string",
"description": "Workspace-label UUID for the `agent:claude` family. Stamped onto every Issue / sub-issue reconciled with CLAUDE_CODE_MODEL (or any AGENT_NAME starting with `claude`) set in the environment.",
"pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$"
},
"codex": {
"type": "string",
"description": "Workspace-label UUID for the `agent:codex` family. Stamped onto every Issue / sub-issue reconciled with CODEX_MODEL (or any AGENT_NAME starting with `codex` / `gpt`) set in the environment.",
"pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$"
}
}
}
}
},
"sync": {
"type": "object",
"description": "Operator-tweakable sync behaviour toggles. Defaults baked into the template; consult linear-config.yml before each reconcile.",
"additionalProperties": false,
"required": ["enabled", "idle_window_days", "emit_summary"],
"properties": {
"enabled": {
"type": "boolean",
"description": "Master kill-switch for hook-driven syncs. When false, /speckit-* after_* hooks skip Linear writes; speckit.linear.push still works on demand. Defaults to true.",
"default": true
},
"idle_window_days": {
"type": "integer",
"description": "Days of filesystem inactivity (no touched spec) before the repo's Project Status flips to 'Paused' per FR-002. 0 disables the auto-flip; operator must use save_project manually.",
"minimum": 0,
"default": 30
},
"emit_summary": {
"type": "boolean",
"description": "Whether the structured reconcile summary (FR-023) is printed to stderr on every run. Defaults to true; set false only for CI parity with quiet modes.",
"default": true
}
}
},
"webhook": {
"type": "object",
"description": "Layer E (GitHub Action) installation status (FR-027, FR-029). Populated by speckit.linear.install when the operator opts in.",
"additionalProperties": false,
"required": ["installed", "workflow_path", "secret_name"],
"properties": {
"installed": {
"type": "boolean",
"description": "True after speckit.linear.install has dropped templates/github-action.yml into .github/workflows/. Flipped manually if the operator removes the workflow.",
"default": false
},
"workflow_path": {
"type": "string",
"description": "Repo-relative path to the installed Action workflow. Locked by FR-027; never customise.",
"const": ".github/workflows/spec-kit-linear-sync.yml"
},
"secret_name": {
"type": "string",
"description": "GitHub repository secret name holding the Linear API token. Locked by FR-029; never customise.",
"const": "LINEAR_API_TOKEN"
}
}
},
"git_hooks": {
"type": "object",
"description": "Local git-hook installation status (FR-033). Populated by speckit.linear.install. Because .git/hooks/ is not versioned, this section is per-clone and re-verified by speckit.linear.install on every run.",
"additionalProperties": false,
"required": ["installed", "hooks"],
"properties": {
"installed": {
"type": "boolean",
"description": "True after the post-checkout / post-commit / post-merge hooks have been written into .git/hooks/. Verified by speckit.linear.install on every install run.",
"default": false
},
"hooks": {
"type": "array",
"description": "List of git hook names this extension installs. Locked to exactly the three FR-033 names.",
"items": {
"type": "string",
"enum": ["post-checkout", "post-commit", "post-merge"]
},
"minItems": 3,
"maxItems": 3,
"uniqueItems": true
}
}
}
}
}