chief-clancy 0.8.22 → 0.9.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/bin/clancy.js +153 -0
- package/package.json +8 -88
- package/README.md +0 -292
- package/dist/bundle/clancy-afk.js +0 -6
- package/dist/bundle/clancy-once.js +0 -239
- package/dist/installer/file-ops/file-ops.d.ts +0 -32
- package/dist/installer/file-ops/file-ops.d.ts.map +0 -1
- package/dist/installer/file-ops/file-ops.js +0 -58
- package/dist/installer/file-ops/file-ops.js.map +0 -1
- package/dist/installer/hook-installer/hook-installer.d.ts +0 -31
- package/dist/installer/hook-installer/hook-installer.d.ts.map +0 -1
- package/dist/installer/hook-installer/hook-installer.js +0 -137
- package/dist/installer/hook-installer/hook-installer.js.map +0 -1
- package/dist/installer/install.d.ts +0 -3
- package/dist/installer/install.d.ts.map +0 -1
- package/dist/installer/install.js +0 -270
- package/dist/installer/install.js.map +0 -1
- package/dist/installer/manifest/manifest.d.ts +0 -41
- package/dist/installer/manifest/manifest.d.ts.map +0 -1
- package/dist/installer/manifest/manifest.js +0 -97
- package/dist/installer/manifest/manifest.js.map +0 -1
- package/dist/installer/prompts/prompts.d.ts +0 -33
- package/dist/installer/prompts/prompts.d.ts.map +0 -1
- package/dist/installer/prompts/prompts.js +0 -55
- package/dist/installer/prompts/prompts.js.map +0 -1
- package/dist/installer/role-filter/role-filter.d.ts +0 -15
- package/dist/installer/role-filter/role-filter.d.ts.map +0 -1
- package/dist/installer/role-filter/role-filter.js +0 -48
- package/dist/installer/role-filter/role-filter.js.map +0 -1
- package/dist/installer/ui/ui.d.ts +0 -9
- package/dist/installer/ui/ui.d.ts.map +0 -1
- package/dist/installer/ui/ui.js +0 -94
- package/dist/installer/ui/ui.js.map +0 -1
- package/dist/scripts/shared/env-parser/env-parser.d.ts +0 -30
- package/dist/scripts/shared/env-parser/env-parser.d.ts.map +0 -1
- package/dist/scripts/shared/env-parser/env-parser.js +0 -64
- package/dist/scripts/shared/env-parser/env-parser.js.map +0 -1
- package/dist/utils/ansi/ansi.d.ts +0 -55
- package/dist/utils/ansi/ansi.d.ts.map +0 -1
- package/dist/utils/ansi/ansi.js +0 -55
- package/dist/utils/ansi/ansi.js.map +0 -1
- package/hooks/clancy-branch-guard.js +0 -128
- package/hooks/clancy-check-update.js +0 -114
- package/hooks/clancy-context-monitor.js +0 -189
- package/hooks/clancy-credential-guard.js +0 -120
- package/hooks/clancy-drift-detector.js +0 -96
- package/hooks/clancy-notification.js +0 -105
- package/hooks/clancy-post-compact.js +0 -53
- package/hooks/clancy-statusline.js +0 -82
- package/hooks/package.json +0 -3
- package/registry/boards.json +0 -44
- package/src/agents/arch-agent.md +0 -72
- package/src/agents/concerns-agent.md +0 -89
- package/src/agents/design-agent.md +0 -130
- package/src/agents/devils-advocate.md +0 -53
- package/src/agents/quality-agent.md +0 -161
- package/src/agents/tech-agent.md +0 -92
- package/src/agents/verification-gate.md +0 -128
- package/src/roles/implementer/commands/dry-run.md +0 -14
- package/src/roles/implementer/commands/once.md +0 -17
- package/src/roles/implementer/commands/run.md +0 -11
- package/src/roles/implementer/workflows/once.md +0 -146
- package/src/roles/implementer/workflows/run.md +0 -127
- package/src/roles/planner/commands/approve-plan.md +0 -10
- package/src/roles/planner/commands/plan.md +0 -20
- package/src/roles/planner/workflows/approve-plan.md +0 -535
- package/src/roles/planner/workflows/plan.md +0 -536
- package/src/roles/reviewer/commands/logs.md +0 -7
- package/src/roles/reviewer/commands/review.md +0 -9
- package/src/roles/reviewer/commands/status.md +0 -9
- package/src/roles/reviewer/workflows/logs.md +0 -104
- package/src/roles/reviewer/workflows/review.md +0 -186
- package/src/roles/reviewer/workflows/status.md +0 -134
- package/src/roles/setup/commands/doctor.md +0 -7
- package/src/roles/setup/commands/help.md +0 -80
- package/src/roles/setup/commands/init.md +0 -7
- package/src/roles/setup/commands/map-codebase.md +0 -16
- package/src/roles/setup/commands/settings.md +0 -7
- package/src/roles/setup/commands/uninstall.md +0 -5
- package/src/roles/setup/commands/update-docs.md +0 -9
- package/src/roles/setup/commands/update.md +0 -12
- package/src/roles/setup/workflows/doctor.md +0 -124
- package/src/roles/setup/workflows/init.md +0 -1073
- package/src/roles/setup/workflows/map-codebase.md +0 -125
- package/src/roles/setup/workflows/scaffold.md +0 -845
- package/src/roles/setup/workflows/settings.md +0 -944
- package/src/roles/setup/workflows/uninstall.md +0 -161
- package/src/roles/setup/workflows/update-docs.md +0 -92
- package/src/roles/setup/workflows/update.md +0 -277
- package/src/roles/strategist/commands/approve-brief.md +0 -21
- package/src/roles/strategist/commands/brief.md +0 -27
- package/src/roles/strategist/workflows/approve-brief.md +0 -834
- package/src/roles/strategist/workflows/brief.md +0 -890
- package/src/templates/CLAUDE.md +0 -87
|
@@ -1,834 +0,0 @@
|
|
|
1
|
-
# Clancy Approve Brief Workflow
|
|
2
|
-
|
|
3
|
-
## Overview
|
|
4
|
-
|
|
5
|
-
Approve a reviewed strategic brief by creating child tickets on the board, linking dependencies, and posting a tracking summary on the parent ticket. Tickets are created sequentially in topological (dependency) order. Partial failures stop immediately — re-run to resume.
|
|
6
|
-
|
|
7
|
-
---
|
|
8
|
-
|
|
9
|
-
## Step 1 — Preflight checks
|
|
10
|
-
|
|
11
|
-
1. Check `.clancy/` exists and `.clancy/.env` is present. If not:
|
|
12
|
-
```
|
|
13
|
-
.clancy/ not found. Run /clancy:init to set up Clancy first.
|
|
14
|
-
```
|
|
15
|
-
Stop.
|
|
16
|
-
|
|
17
|
-
2. Source `.clancy/.env` and check board credentials are present.
|
|
18
|
-
|
|
19
|
-
3. Check `CLANCY_ROLES` includes `strategist` (or env var is unset, which indicates a global install where all roles are available). If `CLANCY_ROLES` is set but does not include `strategist`:
|
|
20
|
-
```
|
|
21
|
-
The Strategist role is not enabled. Add "strategist" to CLANCY_ROLES in .clancy/.env or run /clancy:settings.
|
|
22
|
-
```
|
|
23
|
-
Stop.
|
|
24
|
-
|
|
25
|
-
4. Branch freshness check:
|
|
26
|
-
```bash
|
|
27
|
-
git fetch origin
|
|
28
|
-
```
|
|
29
|
-
Compare HEAD with `origin/$CLANCY_BASE_BRANCH`. If behind:
|
|
30
|
-
|
|
31
|
-
**AFK mode** (`--afk` flag or `CLANCY_MODE=afk`): auto-pull without prompting.
|
|
32
|
-
|
|
33
|
-
**Interactive mode:**
|
|
34
|
-
```
|
|
35
|
-
Behind by N commits. [1] Pull latest [2] Continue [3] Abort
|
|
36
|
-
```
|
|
37
|
-
|
|
38
|
-
---
|
|
39
|
-
|
|
40
|
-
## Step 2 — Load brief
|
|
41
|
-
|
|
42
|
-
Scan `.clancy/briefs/` for unapproved files — `.md` files WITHOUT a corresponding `.md.approved` marker file.
|
|
43
|
-
|
|
44
|
-
### Selection logic
|
|
45
|
-
|
|
46
|
-
Parse the argument (if any) against the unapproved list:
|
|
47
|
-
|
|
48
|
-
**No argument + 0 unapproved briefs:**
|
|
49
|
-
```
|
|
50
|
-
No unapproved briefs found. Run /clancy:brief to generate one.
|
|
51
|
-
```
|
|
52
|
-
Stop.
|
|
53
|
-
|
|
54
|
-
**No argument + 1 unapproved brief:**
|
|
55
|
-
Auto-select the single brief. Continue.
|
|
56
|
-
|
|
57
|
-
**No argument + 2+ unapproved briefs:**
|
|
58
|
-
Display numbered list:
|
|
59
|
-
```
|
|
60
|
-
Multiple unapproved briefs found:
|
|
61
|
-
|
|
62
|
-
[1] {slug-a} ({date}) — {N} tickets — Source: {source}
|
|
63
|
-
[2] {slug-b} ({date}) — {N} tickets — Source: {source}
|
|
64
|
-
|
|
65
|
-
Which brief to approve? [1-N]
|
|
66
|
-
```
|
|
67
|
-
|
|
68
|
-
**Argument is a positive integer (e.g. `2`):**
|
|
69
|
-
Select the Nth unapproved brief by index. If out of range: `Index out of range.` Stop.
|
|
70
|
-
|
|
71
|
-
**Argument matches a ticket identifier (`#\d+`, `[A-Z]+-\d+`):**
|
|
72
|
-
Scan unapproved brief files for a `**Source:**` line containing the identifier. If 0 matches: show available briefs and stop. If 1 match: load it. If 2+ matches: show numbered list and ask.
|
|
73
|
-
|
|
74
|
-
**Argument is other text:**
|
|
75
|
-
Match by slug (filename contains the argument). If 0 matches: `No brief matching "{arg}" found.` Stop. If 1 match: load it. If 2+ matches: list and ask.
|
|
76
|
-
|
|
77
|
-
### Already-approved guard
|
|
78
|
-
|
|
79
|
-
After loading, verify the `.md.approved` marker does not exist. If it does:
|
|
80
|
-
```
|
|
81
|
-
Brief "{slug}" is already approved. No action needed.
|
|
82
|
-
```
|
|
83
|
-
Stop.
|
|
84
|
-
|
|
85
|
-
---
|
|
86
|
-
|
|
87
|
-
## Step 3 — Parse decomposition table
|
|
88
|
-
|
|
89
|
-
Read the `## Ticket Decomposition` table from the brief file. Extract each row:
|
|
90
|
-
|
|
91
|
-
| Column | Field |
|
|
92
|
-
|--------|-------|
|
|
93
|
-
| `#` | Sequence number |
|
|
94
|
-
| `Title` | Ticket title |
|
|
95
|
-
| `Description` | 1-2 sentence description |
|
|
96
|
-
| `Size` | S / M / L |
|
|
97
|
-
| `Dependencies` | References like `#1`, `#3` |
|
|
98
|
-
| `Mode` | AFK or HITL |
|
|
99
|
-
| `Ticket` | Board key (if partially created) |
|
|
100
|
-
|
|
101
|
-
### Validation
|
|
102
|
-
|
|
103
|
-
- **0 tickets:** `No tickets found in the decomposition table. Add at least one ticket to the brief before approving.` Stop.
|
|
104
|
-
- **>10 tickets:** Warn: `Brief has {N} tickets (max recommended: 10). Large decompositions may indicate the idea should be split further.` Continue — advisory only.
|
|
105
|
-
- **Tickets already in Ticket column:** These were created in a prior partial run. Mark them for skipping during creation.
|
|
106
|
-
- **Circular dependencies:** Run cycle detection on the dependency graph. If a cycle exists: `Circular dependency detected between #{N} and #{M}. Edit the brief to resolve, then re-run.` Stop.
|
|
107
|
-
|
|
108
|
-
### Topological sort
|
|
109
|
-
|
|
110
|
-
Order tickets by their dependency graph so blockers are created before dependents. This ensures blocking relationships can be linked immediately after creation.
|
|
111
|
-
|
|
112
|
-
---
|
|
113
|
-
|
|
114
|
-
## Step 4 — Confirm with user
|
|
115
|
-
|
|
116
|
-
Display the ticket list in dependency order:
|
|
117
|
-
|
|
118
|
-
```
|
|
119
|
-
Clancy — Approve Brief
|
|
120
|
-
|
|
121
|
-
Brief: {slug}
|
|
122
|
-
Parent: {ticket key or "none (standalone)"}
|
|
123
|
-
|
|
124
|
-
Tickets to create (dependency order):
|
|
125
|
-
#1 [S] [AFK] Title — No deps
|
|
126
|
-
#2 [M] [HITL] Title — No deps
|
|
127
|
-
#3 [M] [AFK] Title — After #2
|
|
128
|
-
#4 [S] [AFK] Title — After #1, #3
|
|
129
|
-
|
|
130
|
-
Labels: {list of labels to apply}
|
|
131
|
-
Issue type: {type} (Jira only)
|
|
132
|
-
AFK-ready: {X} | Needs human: {Y}
|
|
133
|
-
|
|
134
|
-
Create {N} tickets? [Y/n]
|
|
135
|
-
```
|
|
136
|
-
|
|
137
|
-
**AFK mode:** If running in AFK mode (`--afk` flag OR `CLANCY_MODE=afk`), skip the confirmation prompt and auto-confirm. Display the ticket list for logging purposes but proceed without waiting for input.
|
|
138
|
-
|
|
139
|
-
If user declines (interactive only): `Cancelled. No changes made.` Stop.
|
|
140
|
-
|
|
141
|
-
---
|
|
142
|
-
|
|
143
|
-
## Step 4a — Dry-run check
|
|
144
|
-
|
|
145
|
-
If `--dry-run` flag is set:
|
|
146
|
-
```
|
|
147
|
-
Dry run complete. No tickets created.
|
|
148
|
-
```
|
|
149
|
-
Stop. No API calls beyond preflight.
|
|
150
|
-
|
|
151
|
-
---
|
|
152
|
-
|
|
153
|
-
## Step 5 — Resolve parent
|
|
154
|
-
|
|
155
|
-
The parent ticket is where child tickets are linked. Determine the parent from one of these sources (in priority order):
|
|
156
|
-
|
|
157
|
-
### Source 1 — Board-sourced brief
|
|
158
|
-
|
|
159
|
-
If the brief's `**Source:**` line contains a board ticket identifier (e.g. `[#50]`, `[PROJ-200]`, `[ENG-42]`), that ticket is the parent. Fetch it to validate:
|
|
160
|
-
|
|
161
|
-
#### GitHub
|
|
162
|
-
```bash
|
|
163
|
-
curl -s \
|
|
164
|
-
-H "Authorization: Bearer $GITHUB_TOKEN" \
|
|
165
|
-
-H "Accept: application/vnd.github+json" \
|
|
166
|
-
-H "X-GitHub-Api-Version: 2022-11-28" \
|
|
167
|
-
"https://api.github.com/repos/$GITHUB_REPO/issues/$PARENT_NUMBER"
|
|
168
|
-
```
|
|
169
|
-
|
|
170
|
-
Check: `pull_request` present? -> `Parent #{N} is a PR, not an issue.` Stop. `state == "closed"`? -> Warn, ask `[y/N]`.
|
|
171
|
-
|
|
172
|
-
#### Jira
|
|
173
|
-
```bash
|
|
174
|
-
curl -s \
|
|
175
|
-
-u "$JIRA_USER:$JIRA_API_TOKEN" \
|
|
176
|
-
-H "Accept: application/json" \
|
|
177
|
-
"$JIRA_BASE_URL/rest/api/3/issue/$PARENT_KEY?fields=summary,status,issuetype,priority"
|
|
178
|
-
```
|
|
179
|
-
|
|
180
|
-
Check: `status.statusCategory.key == "done"`? -> Warn, ask `[y/N]`. Note if `issuetype.name == "Epic"` (ideal case for parent linking).
|
|
181
|
-
|
|
182
|
-
#### Linear
|
|
183
|
-
```graphql
|
|
184
|
-
query {
|
|
185
|
-
issues(filter: { identifier: { eq: "$PARENT_KEY" } }) {
|
|
186
|
-
nodes {
|
|
187
|
-
id identifier title
|
|
188
|
-
state { type }
|
|
189
|
-
team { id key }
|
|
190
|
-
children { nodes { id identifier title } }
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
```
|
|
195
|
-
|
|
196
|
-
Check: `state.type == "completed"` or `"canceled"`? -> Warn, ask `[y/N]`. `team.id != LINEAR_TEAM_ID`? -> Warn about cross-team children, ask `[Y/n]`.
|
|
197
|
-
|
|
198
|
-
### Source 2 — `--epic` flag
|
|
199
|
-
|
|
200
|
-
If `--epic` is provided (e.g. `--epic PROJ-200`, `--epic #100`, `--epic ENG-42`), validate the target using the same checks as Source 1. If not found or is Done: stop.
|
|
201
|
-
|
|
202
|
-
Note: `--epic` is ignored for board-sourced briefs (the source ticket IS the parent).
|
|
203
|
-
|
|
204
|
-
### Source 3 — `CLANCY_BRIEF_EPIC` default
|
|
205
|
-
|
|
206
|
-
If no board source and no `--epic` flag, check `.clancy/.env` for `CLANCY_BRIEF_EPIC`. If set, validate and use it as the parent.
|
|
207
|
-
|
|
208
|
-
### Source 4 — No parent (standalone)
|
|
209
|
-
|
|
210
|
-
If none of the above apply:
|
|
211
|
-
```
|
|
212
|
-
No parent specified. Tickets will be created as standalone.
|
|
213
|
-
Use --epic {KEY} to attach to a parent.
|
|
214
|
-
```
|
|
215
|
-
Continue — omit parent fields from creation payloads. No tracking comment will be posted.
|
|
216
|
-
|
|
217
|
-
---
|
|
218
|
-
|
|
219
|
-
## Step 6 — Look up queue state / labels
|
|
220
|
-
|
|
221
|
-
Platform-specific pre-creation lookups.
|
|
222
|
-
|
|
223
|
-
### GitHub
|
|
224
|
-
|
|
225
|
-
**Pipeline label for children — this is mandatory, always apply a pipeline label to every child ticket.** Determine which label to use:
|
|
226
|
-
- `--skip-plan` flag → use `CLANCY_LABEL_BUILD` from `.clancy/.env` if set, otherwise `clancy:build`
|
|
227
|
-
- Planner role enabled (`CLANCY_ROLES` includes `planner`) → use `CLANCY_LABEL_PLAN` from `.clancy/.env` if set, otherwise `clancy:plan`
|
|
228
|
-
- Planner role NOT enabled → use `CLANCY_LABEL_BUILD` from `.clancy/.env` if set, otherwise `clancy:build`
|
|
229
|
-
Ensure the label exists on the board (create it if missing), then add it to each child ticket.
|
|
230
|
-
|
|
231
|
-
**Labels to apply per ticket:**
|
|
232
|
-
- The pipeline label determined above (`CLANCY_LABEL_PLAN` or `CLANCY_LABEL_BUILD`) — replaces `CLANCY_LABEL` on children
|
|
233
|
-
- `component:{CLANCY_COMPONENT}` (if `CLANCY_COMPONENT` set)
|
|
234
|
-
- `size:{S|M|L}` — from decomposition table
|
|
235
|
-
- `clancy:afk` or `clancy:hitl` — from Mode column
|
|
236
|
-
|
|
237
|
-
Note: `CLANCY_LABEL` is NOT applied to child tickets when pipeline labels are active. The pipeline label (`clancy:plan` or `clancy:build`) serves as the queue identifier.
|
|
238
|
-
|
|
239
|
-
**Label pre-creation:** For each unique label, attempt to create it on the repo. If it already exists, GitHub returns 422 — ignore that error. If 403 (no admin access), note the label as unavailable.
|
|
240
|
-
|
|
241
|
-
```bash
|
|
242
|
-
curl -s \
|
|
243
|
-
-H "Authorization: Bearer $GITHUB_TOKEN" \
|
|
244
|
-
-H "Accept: application/vnd.github+json" \
|
|
245
|
-
-H "X-GitHub-Api-Version: 2022-11-28" \
|
|
246
|
-
-H "Content-Type: application/json" \
|
|
247
|
-
-X POST \
|
|
248
|
-
"https://api.github.com/repos/$GITHUB_REPO/labels" \
|
|
249
|
-
-d '{"name": "$LABEL_NAME", "color": "d4c5f9"}'
|
|
250
|
-
```
|
|
251
|
-
|
|
252
|
-
If label creation fails with 403: warn, continue without that label.
|
|
253
|
-
|
|
254
|
-
**Resolve username** for assignee:
|
|
255
|
-
```bash
|
|
256
|
-
curl -s \
|
|
257
|
-
-H "Authorization: Bearer $GITHUB_TOKEN" \
|
|
258
|
-
-H "Accept: application/vnd.github+json" \
|
|
259
|
-
"https://api.github.com/user"
|
|
260
|
-
```
|
|
261
|
-
Cache the `login` field for all ticket creations.
|
|
262
|
-
|
|
263
|
-
### Jira
|
|
264
|
-
|
|
265
|
-
**Validate issue type exists in project:**
|
|
266
|
-
```bash
|
|
267
|
-
curl -s \
|
|
268
|
-
-u "$JIRA_USER:$JIRA_API_TOKEN" \
|
|
269
|
-
-H "Accept: application/json" \
|
|
270
|
-
"$JIRA_BASE_URL/rest/api/3/issue/createmeta/$JIRA_PROJECT_KEY/issuetypes"
|
|
271
|
-
```
|
|
272
|
-
|
|
273
|
-
Look for `CLANCY_BRIEF_ISSUE_TYPE` (default: `Task`). If not found:
|
|
274
|
-
```
|
|
275
|
-
Issue type "{type}" not available in project {PROJ}. Available types: {list}.
|
|
276
|
-
Set CLANCY_BRIEF_ISSUE_TYPE in .clancy/.env.
|
|
277
|
-
```
|
|
278
|
-
Stop.
|
|
279
|
-
|
|
280
|
-
**Pipeline label for children — mandatory, same logic as GitHub.** Use `CLANCY_LABEL_PLAN` or `CLANCY_LABEL_BUILD` from `.clancy/.env` (defaults: `clancy:plan` / `clancy:build`). Always apply to every child ticket.
|
|
281
|
-
|
|
282
|
-
**Labels:** Jira auto-creates labels — no pre-creation needed. Apply: the pipeline label determined above, `clancy:afk` or `clancy:hitl`. `CLANCY_LABEL` is NOT applied to children when pipeline labels are active.
|
|
283
|
-
|
|
284
|
-
**Components:** If `CLANCY_COMPONENT` is set, it maps to the Jira `components` field.
|
|
285
|
-
|
|
286
|
-
**Priority:** Inherit from parent ticket if available. Otherwise omit (Jira uses project default).
|
|
287
|
-
|
|
288
|
-
### Linear
|
|
289
|
-
|
|
290
|
-
**Look up backlog state UUID:**
|
|
291
|
-
```graphql
|
|
292
|
-
query {
|
|
293
|
-
workflowStates(filter: {
|
|
294
|
-
team: { id: { eq: "$LINEAR_TEAM_ID" } }
|
|
295
|
-
type: { eq: "backlog" }
|
|
296
|
-
}) {
|
|
297
|
-
nodes { id name }
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
```
|
|
301
|
-
|
|
302
|
-
Use `nodes[0].id`. If empty, fall back to `triage` type, then `unstarted`, then any first state. If truly nothing found, warn and use the team default.
|
|
303
|
-
|
|
304
|
-
**Look up label UUIDs:**
|
|
305
|
-
```graphql
|
|
306
|
-
query {
|
|
307
|
-
team(id: "$LINEAR_TEAM_ID") {
|
|
308
|
-
labels { nodes { id name } }
|
|
309
|
-
}
|
|
310
|
-
}
|
|
311
|
-
```
|
|
312
|
-
|
|
313
|
-
**Pipeline label for children — mandatory, same logic as GitHub/Jira.** Use `CLANCY_LABEL_PLAN` or `CLANCY_LABEL_BUILD` from `.clancy/.env` (defaults: `clancy:plan` / `clancy:build`). Always apply to every child ticket.
|
|
314
|
-
|
|
315
|
-
For each required label (the pipeline label, `component:{CLANCY_COMPONENT}`, `clancy:afk`, `clancy:hitl`): search by exact name. `CLANCY_LABEL` is NOT applied to children when pipeline labels are active. If not found in team labels, check workspace labels:
|
|
316
|
-
|
|
317
|
-
```graphql
|
|
318
|
-
query {
|
|
319
|
-
issueLabels(filter: { name: { eq: "$LABEL_NAME" } }) {
|
|
320
|
-
nodes { id name }
|
|
321
|
-
}
|
|
322
|
-
}
|
|
323
|
-
```
|
|
324
|
-
|
|
325
|
-
If still not found, auto-create at team level:
|
|
326
|
-
```graphql
|
|
327
|
-
mutation {
|
|
328
|
-
issueLabelCreate(input: { teamId: "$LINEAR_TEAM_ID", name: "$LABEL_NAME" }) {
|
|
329
|
-
success
|
|
330
|
-
issueLabel { id name }
|
|
331
|
-
}
|
|
332
|
-
}
|
|
333
|
-
```
|
|
334
|
-
|
|
335
|
-
On failure to create label: warn, continue without it.
|
|
336
|
-
|
|
337
|
-
---
|
|
338
|
-
|
|
339
|
-
## Step 6a — Pre-creation race check
|
|
340
|
-
|
|
341
|
-
Create the `.approved` marker file with exclusive create (O_EXCL) to prevent concurrent approval:
|
|
342
|
-
|
|
343
|
-
```
|
|
344
|
-
Marker path: .clancy/briefs/{filename}.approved
|
|
345
|
-
```
|
|
346
|
-
|
|
347
|
-
If the file already exists (`EEXIST`):
|
|
348
|
-
```
|
|
349
|
-
Brief is being approved by another process.
|
|
350
|
-
```
|
|
351
|
-
Stop.
|
|
352
|
-
|
|
353
|
-
If creation succeeds: the marker acts as a lock. **If ticket creation fails later, delete this marker** (partial failure = not approved).
|
|
354
|
-
|
|
355
|
-
---
|
|
356
|
-
|
|
357
|
-
## Step 7 — Create child tickets
|
|
358
|
-
|
|
359
|
-
Create tickets **sequentially** in topological (dependency) order. Wait **500ms** between each creation to respect rate limits.
|
|
360
|
-
|
|
361
|
-
For each ticket in dependency order:
|
|
362
|
-
|
|
363
|
-
1. **Check if already created:** If the Ticket column has a value from a prior partial run, skip this ticket. Display: `[{i}/{N}] skip {KEY} — already exists`
|
|
364
|
-
|
|
365
|
-
2. **Build the creation payload** (platform-specific — see below).
|
|
366
|
-
|
|
367
|
-
3. **Send the API request.**
|
|
368
|
-
|
|
369
|
-
4. **On success:** Record the created key/number. Map the decomposition `#N` to the board key for dependency linking. Display: `[{i}/{N}] + {KEY} — {Title} [{Mode}]`
|
|
370
|
-
|
|
371
|
-
5. **On failure:** STOP immediately. Do NOT attempt remaining tickets. See partial failure handling in Step 10.
|
|
372
|
-
|
|
373
|
-
6. **Wait 500ms** before the next creation.
|
|
374
|
-
|
|
375
|
-
### GitHub — POST /repos/{owner}/{repo}/issues
|
|
376
|
-
|
|
377
|
-
```bash
|
|
378
|
-
curl -s \
|
|
379
|
-
-H "Authorization: Bearer $GITHUB_TOKEN" \
|
|
380
|
-
-H "Accept: application/vnd.github+json" \
|
|
381
|
-
-H "X-GitHub-Api-Version: 2022-11-28" \
|
|
382
|
-
-H "Content-Type: application/json" \
|
|
383
|
-
-X POST \
|
|
384
|
-
"https://api.github.com/repos/$GITHUB_REPO/issues" \
|
|
385
|
-
-d '{
|
|
386
|
-
"title": "{ticket title}",
|
|
387
|
-
"body": "Epic: #{parent_number}\n\n## {Title}\n\n{Description}\n\n---\n\n**Parent:** #{parent_number}\n**Brief:** {slug}\n**Size:** {S|M|L}\n\n### Dependencies\n\n{Depends on #NN lines or None}\n\n---\n\n*Created by Clancy from strategic brief.*",
|
|
388
|
-
"labels": ["{PIPELINE_LABEL}", "size:{size}", "clancy:{mode}", ...],
|
|
389
|
-
"assignees": ["{resolved_username}"]
|
|
390
|
-
}'
|
|
391
|
-
```
|
|
392
|
-
|
|
393
|
-
The `Epic: #{parent_number}` line is always the FIRST line of the body — this enables cross-platform epic completion detection.
|
|
394
|
-
|
|
395
|
-
Dependencies in the body use resolved issue numbers: `Depends on #51` (not decomposition indices).
|
|
396
|
-
|
|
397
|
-
If no parent: omit `Epic:` and `Parent:` lines, omit `assignees` if not resolvable.
|
|
398
|
-
|
|
399
|
-
**On 422 (label validation error):** Retry without the invalid label(s). Warn: `Label "{name}" does not exist. Created issue without it.`
|
|
400
|
-
|
|
401
|
-
**On 429 (rate limited):** Check `X-RateLimit-Reset` header. Wait until reset, retry once. If still limited: stop, enter partial failure flow.
|
|
402
|
-
|
|
403
|
-
### Jira — POST /rest/api/3/issue
|
|
404
|
-
|
|
405
|
-
```bash
|
|
406
|
-
curl -s \
|
|
407
|
-
-u "$JIRA_USER:$JIRA_API_TOKEN" \
|
|
408
|
-
-H "Content-Type: application/json" \
|
|
409
|
-
-H "Accept: application/json" \
|
|
410
|
-
-X POST \
|
|
411
|
-
"$JIRA_BASE_URL/rest/api/3/issue" \
|
|
412
|
-
-d '{
|
|
413
|
-
"fields": {
|
|
414
|
-
"project": { "key": "$JIRA_PROJECT_KEY" },
|
|
415
|
-
"summary": "{ticket title}",
|
|
416
|
-
"description": {
|
|
417
|
-
"version": 1,
|
|
418
|
-
"type": "doc",
|
|
419
|
-
"content": [
|
|
420
|
-
{
|
|
421
|
-
"type": "paragraph",
|
|
422
|
-
"content": [
|
|
423
|
-
{ "type": "text", "text": "Epic: {PARENT_KEY}\n\n{Description}" }
|
|
424
|
-
]
|
|
425
|
-
}
|
|
426
|
-
]
|
|
427
|
-
},
|
|
428
|
-
"issuetype": { "name": "{CLANCY_BRIEF_ISSUE_TYPE or Task}" },
|
|
429
|
-
"parent": { "key": "{PARENT_KEY}" },
|
|
430
|
-
"labels": ["{PIPELINE_LABEL}", "clancy:{mode}"]
|
|
431
|
-
}
|
|
432
|
-
}'
|
|
433
|
-
```
|
|
434
|
-
|
|
435
|
-
**Conditional fields (include only when set):**
|
|
436
|
-
- `CLANCY_COMPONENT` -> `"components": [{ "name": "{value}" }]`
|
|
437
|
-
- Parent has priority -> `"priority": { "name": "{priority}" }`
|
|
438
|
-
|
|
439
|
-
**Parent field fallback (classic projects):**
|
|
440
|
-
If the API returns 400 with an error mentioning the `parent` field, retry with `customfield_10014` instead:
|
|
441
|
-
```json
|
|
442
|
-
"customfield_10014": "{PARENT_KEY}"
|
|
443
|
-
```
|
|
444
|
-
Cache which field works for the remaining tickets in this batch.
|
|
445
|
-
|
|
446
|
-
If both `parent` and `customfield_10014` fail:
|
|
447
|
-
```
|
|
448
|
-
Could not set parent. Continue creating tickets without parent? [y/N]
|
|
449
|
-
```
|
|
450
|
-
Default: N (stop). If Y: create remaining tickets without parent field.
|
|
451
|
-
|
|
452
|
-
**On 400 (component not found):** Retry without `components` field. Warn.
|
|
453
|
-
|
|
454
|
-
**On 429 (rate limited):** Honour `Retry-After` header. Wait, retry once. If still 429: stop, enter partial failure flow.
|
|
455
|
-
|
|
456
|
-
### Linear — issueCreate mutation
|
|
457
|
-
|
|
458
|
-
```graphql
|
|
459
|
-
mutation {
|
|
460
|
-
issueCreate(input: {
|
|
461
|
-
teamId: "$LINEAR_TEAM_ID"
|
|
462
|
-
title: "{ticket title}"
|
|
463
|
-
description: "Epic: {PARENT_IDENTIFIER}\n\n{Description}"
|
|
464
|
-
parentId: "{PARENT_UUID}"
|
|
465
|
-
stateId: "{BACKLOG_STATE_UUID}"
|
|
466
|
-
labelIds: ["{label UUIDs}"]
|
|
467
|
-
priority: 0
|
|
468
|
-
}) {
|
|
469
|
-
success
|
|
470
|
-
issue {
|
|
471
|
-
id
|
|
472
|
-
identifier
|
|
473
|
-
title
|
|
474
|
-
url
|
|
475
|
-
}
|
|
476
|
-
}
|
|
477
|
-
}
|
|
478
|
-
```
|
|
479
|
-
|
|
480
|
-
If no parent: omit `parentId`.
|
|
481
|
-
|
|
482
|
-
**On rate limit (RATELIMITED error code):** Wait 60s, retry once. If still limited: stop, enter partial failure flow.
|
|
483
|
-
|
|
484
|
-
---
|
|
485
|
-
|
|
486
|
-
## Step 8 — Link dependencies
|
|
487
|
-
|
|
488
|
-
For each ticket with dependencies, create blocking relationships on the board. This is **best-effort** — warn on failure, do not stop.
|
|
489
|
-
|
|
490
|
-
Skip links involving uncreated tickets (from partial failures).
|
|
491
|
-
|
|
492
|
-
### GitHub
|
|
493
|
-
|
|
494
|
-
No separate API call needed. Dependencies are embedded in the issue body as `Depends on #{number}` text. GitHub auto-creates cross-references from the `#N` mentions.
|
|
495
|
-
|
|
496
|
-
### Jira — POST /rest/api/3/issueLink
|
|
497
|
-
|
|
498
|
-
For each dependency (e.g. ticket #3 depends on #2):
|
|
499
|
-
|
|
500
|
-
```bash
|
|
501
|
-
curl -s \
|
|
502
|
-
-u "$JIRA_USER:$JIRA_API_TOKEN" \
|
|
503
|
-
-H "Content-Type: application/json" \
|
|
504
|
-
-H "Accept: application/json" \
|
|
505
|
-
-X POST \
|
|
506
|
-
"$JIRA_BASE_URL/rest/api/3/issueLink" \
|
|
507
|
-
-d '{
|
|
508
|
-
"type": { "name": "Blocks" },
|
|
509
|
-
"inwardIssue": { "key": "{BLOCKER_KEY}" },
|
|
510
|
-
"outwardIssue": { "key": "{DEPENDENT_KEY}" }
|
|
511
|
-
}'
|
|
512
|
-
```
|
|
513
|
-
|
|
514
|
-
`inwardIssue` = the blocker, `outwardIssue` = the dependent ticket.
|
|
515
|
-
|
|
516
|
-
Wait **200ms** between each link creation.
|
|
517
|
-
|
|
518
|
-
On failure: `Could not link {DEPENDENT} -> {BLOCKER} (dependency). Link manually if needed.`
|
|
519
|
-
|
|
520
|
-
### Linear — issueRelationCreate mutation
|
|
521
|
-
|
|
522
|
-
```graphql
|
|
523
|
-
mutation {
|
|
524
|
-
issueRelationCreate(input: {
|
|
525
|
-
issueId: "{DEPENDENT_UUID}"
|
|
526
|
-
relatedIssueId: "{BLOCKER_UUID}"
|
|
527
|
-
type: blockedBy
|
|
528
|
-
}) {
|
|
529
|
-
success
|
|
530
|
-
}
|
|
531
|
-
}
|
|
532
|
-
```
|
|
533
|
-
|
|
534
|
-
`issueId` = the dependent ticket, `relatedIssueId` = the blocker.
|
|
535
|
-
|
|
536
|
-
Wait **200ms** between each relation creation.
|
|
537
|
-
|
|
538
|
-
On failure: `Could not link {DEPENDENT} -> {BLOCKER}. Add manually in Linear.`
|
|
539
|
-
|
|
540
|
-
---
|
|
541
|
-
|
|
542
|
-
## Step 9 — Update brief file
|
|
543
|
-
|
|
544
|
-
After creating tickets, update the local brief file:
|
|
545
|
-
|
|
546
|
-
1. **Add ticket keys** to the `Ticket` column in the decomposition table:
|
|
547
|
-
```markdown
|
|
548
|
-
| # | Title | Description | Size | Dependencies | Mode | Ticket |
|
|
549
|
-
|---|-------|-------------|------|--------------|------|--------|
|
|
550
|
-
| 1 | Set up route structure | ... | S | None | AFK | PROJ-201 |
|
|
551
|
-
| 2 | SSO login flow | ... | M | None | HITL | PROJ-202 |
|
|
552
|
-
```
|
|
553
|
-
|
|
554
|
-
2. **Update status** from `Draft` to `Approved` (in the `**Status:**` line if present).
|
|
555
|
-
|
|
556
|
-
---
|
|
557
|
-
|
|
558
|
-
## Step 10 — Mark approved
|
|
559
|
-
|
|
560
|
-
Check whether all tickets were created successfully:
|
|
561
|
-
|
|
562
|
-
**All created:**
|
|
563
|
-
- The `.approved` marker file (created in Step 6a) stays in place.
|
|
564
|
-
|
|
565
|
-
**Partial failure (some tickets failed):**
|
|
566
|
-
- **DELETE** the `.approved` marker file. Partial failure = not approved.
|
|
567
|
-
- Update the brief file with the tickets that WERE created (Step 9 still applies).
|
|
568
|
-
- Display partial failure summary:
|
|
569
|
-
```
|
|
570
|
-
Partial: {M} of {N} tickets created
|
|
571
|
-
|
|
572
|
-
PROJ-201 [S] Set up route structure
|
|
573
|
-
PROJ-202 [M] SSO login flow
|
|
574
|
-
X #3 Role-based access control — FAILED ({error})
|
|
575
|
-
- #4-#{N} not attempted
|
|
576
|
-
|
|
577
|
-
Created tickets are live on the board. To complete:
|
|
578
|
-
1. Fix the issue (check board status/permissions)
|
|
579
|
-
2. Re-run /clancy:approve-brief to resume creating remaining tickets
|
|
580
|
-
```
|
|
581
|
-
- Log: `YYYY-MM-DD HH:MM | APPROVE_BRIEF | {slug} | PARTIAL — {M} of {N} created`
|
|
582
|
-
- Stop (do not post tracking comment for partial failures).
|
|
583
|
-
|
|
584
|
-
---
|
|
585
|
-
|
|
586
|
-
## Step 11 — Post tracking summary on parent
|
|
587
|
-
|
|
588
|
-
Only if a parent ticket exists AND all tickets were created successfully.
|
|
589
|
-
|
|
590
|
-
Post a comment on the parent ticket listing all created children.
|
|
591
|
-
|
|
592
|
-
### GitHub
|
|
593
|
-
|
|
594
|
-
```bash
|
|
595
|
-
curl -s \
|
|
596
|
-
-H "Authorization: Bearer $GITHUB_TOKEN" \
|
|
597
|
-
-H "Accept: application/vnd.github+json" \
|
|
598
|
-
-H "X-GitHub-Api-Version: 2022-11-28" \
|
|
599
|
-
-H "Content-Type: application/json" \
|
|
600
|
-
-X POST \
|
|
601
|
-
"https://api.github.com/repos/$GITHUB_REPO/issues/$PARENT_NUMBER/comments" \
|
|
602
|
-
-d '{"body": "<tracking comment markdown>"}'
|
|
603
|
-
```
|
|
604
|
-
|
|
605
|
-
### Jira
|
|
606
|
-
|
|
607
|
-
```bash
|
|
608
|
-
curl -s \
|
|
609
|
-
-u "$JIRA_USER:$JIRA_API_TOKEN" \
|
|
610
|
-
-H "Content-Type: application/json" \
|
|
611
|
-
-H "Accept: application/json" \
|
|
612
|
-
-X POST \
|
|
613
|
-
"$JIRA_BASE_URL/rest/api/3/issue/$PARENT_KEY/comment" \
|
|
614
|
-
-d '{"body": { "version": 1, "type": "doc", "content": [/* ADF table */] }}'
|
|
615
|
-
```
|
|
616
|
-
|
|
617
|
-
### Linear
|
|
618
|
-
|
|
619
|
-
```graphql
|
|
620
|
-
mutation {
|
|
621
|
-
commentCreate(input: {
|
|
622
|
-
issueId: "$PARENT_UUID"
|
|
623
|
-
body: "<tracking comment markdown>"
|
|
624
|
-
}) {
|
|
625
|
-
success
|
|
626
|
-
}
|
|
627
|
-
}
|
|
628
|
-
```
|
|
629
|
-
|
|
630
|
-
### Tracking comment format
|
|
631
|
-
|
|
632
|
-
```markdown
|
|
633
|
-
## Clancy — Approved Tickets
|
|
634
|
-
|
|
635
|
-
| # | Ticket | Title | Size | Mode |
|
|
636
|
-
|---|--------|-------|------|------|
|
|
637
|
-
| 1 | {KEY} | {Title} | S | AFK |
|
|
638
|
-
| 2 | {KEY} | {Title} | M | HITL |
|
|
639
|
-
| 3 | {KEY} | {Title} | M | AFK |
|
|
640
|
-
|
|
641
|
-
Dependencies linked: {N}
|
|
642
|
-
Created by Clancy on {YYYY-MM-DD}.
|
|
643
|
-
```
|
|
644
|
-
|
|
645
|
-
**On failure:** Warn: `Could not post tracking summary on {PARENT}. Tickets are created regardless.` Continue — best-effort.
|
|
646
|
-
|
|
647
|
-
---
|
|
648
|
-
|
|
649
|
-
## Step 11a — Remove brief label from parent
|
|
650
|
-
|
|
651
|
-
Only if a parent ticket exists AND all tickets were created successfully. Remove the brief label from the parent ticket. Best-effort — warn on failure, never stop.
|
|
652
|
-
|
|
653
|
-
Use `CLANCY_LABEL_BRIEF` from `.clancy/.env` if set, otherwise `clancy:brief`.
|
|
654
|
-
|
|
655
|
-
### GitHub
|
|
656
|
-
|
|
657
|
-
```bash
|
|
658
|
-
curl -s \
|
|
659
|
-
-H "Authorization: Bearer $GITHUB_TOKEN" \
|
|
660
|
-
-H "Accept: application/vnd.github+json" \
|
|
661
|
-
-H "X-GitHub-Api-Version: 2022-11-28" \
|
|
662
|
-
-X DELETE \
|
|
663
|
-
"https://api.github.com/repos/$GITHUB_REPO/issues/$PARENT_NUMBER/labels/$(echo $CLANCY_LABEL_BRIEF | jq -Rr @uri)"
|
|
664
|
-
```
|
|
665
|
-
|
|
666
|
-
Ignore 404 (label may not be present on the parent).
|
|
667
|
-
|
|
668
|
-
### Jira
|
|
669
|
-
|
|
670
|
-
```bash
|
|
671
|
-
CURRENT_LABELS=$(curl -s \
|
|
672
|
-
-u "$JIRA_USER:$JIRA_API_TOKEN" \
|
|
673
|
-
-H "Accept: application/json" \
|
|
674
|
-
"$JIRA_BASE_URL/rest/api/3/issue/$PARENT_KEY?fields=labels" | jq -r '.fields.labels')
|
|
675
|
-
|
|
676
|
-
UPDATED_LABELS=$(echo "$CURRENT_LABELS" | jq --arg brief "$CLANCY_LABEL_BRIEF" '[.[] | select(. != $brief)]')
|
|
677
|
-
|
|
678
|
-
curl -s \
|
|
679
|
-
-u "$JIRA_USER:$JIRA_API_TOKEN" \
|
|
680
|
-
-X PUT \
|
|
681
|
-
-H "Content-Type: application/json" \
|
|
682
|
-
"$JIRA_BASE_URL/rest/api/3/issue/$PARENT_KEY" \
|
|
683
|
-
-d "{\"fields\": {\"labels\": $UPDATED_LABELS}}"
|
|
684
|
-
```
|
|
685
|
-
|
|
686
|
-
### Linear
|
|
687
|
-
|
|
688
|
-
```bash
|
|
689
|
-
# Fetch current label IDs on the parent, remove the brief label ID, issueUpdate
|
|
690
|
-
# Use the same pattern as brief.md Step 10a — query labels, filter, update
|
|
691
|
-
```
|
|
692
|
-
|
|
693
|
-
### On failure
|
|
694
|
-
|
|
695
|
-
```
|
|
696
|
-
⚠️ Could not remove brief label from {PARENT_KEY}. Remove it manually if needed.
|
|
697
|
-
```
|
|
698
|
-
|
|
699
|
-
Continue — do not stop.
|
|
700
|
-
|
|
701
|
-
---
|
|
702
|
-
|
|
703
|
-
## Step 12 — Display summary
|
|
704
|
-
|
|
705
|
-
Show the final result:
|
|
706
|
-
|
|
707
|
-
```
|
|
708
|
-
{N} tickets created under {PARENT_KEY}
|
|
709
|
-
|
|
710
|
-
{KEY} [{Size}] [{Mode}] {Title}
|
|
711
|
-
{KEY} [{Size}] [{Mode}] {Title}
|
|
712
|
-
{KEY} [{Size}] [{Mode}] {Title}
|
|
713
|
-
|
|
714
|
-
Dependencies linked: {N}
|
|
715
|
-
AFK-ready: {X} | Needs human: {Y}
|
|
716
|
-
Epic branch: epic/{parent-key-lowercase}
|
|
717
|
-
|
|
718
|
-
Next: run /clancy:plan to generate implementation plans.
|
|
719
|
-
|
|
720
|
-
"We've got ourselves a case."
|
|
721
|
-
```
|
|
722
|
-
|
|
723
|
-
If no parent:
|
|
724
|
-
```
|
|
725
|
-
{N} standalone tickets created.
|
|
726
|
-
...
|
|
727
|
-
Next: run /clancy:plan to generate implementation plans.
|
|
728
|
-
|
|
729
|
-
"We've got ourselves a case."
|
|
730
|
-
```
|
|
731
|
-
|
|
732
|
-
---
|
|
733
|
-
|
|
734
|
-
## Step 13 — Log
|
|
735
|
-
|
|
736
|
-
Append to `.clancy/progress.txt`:
|
|
737
|
-
|
|
738
|
-
```
|
|
739
|
-
YYYY-MM-DD HH:MM | APPROVE_BRIEF | {slug} | {N} tickets created
|
|
740
|
-
```
|
|
741
|
-
|
|
742
|
-
---
|
|
743
|
-
|
|
744
|
-
## Error handling reference
|
|
745
|
-
|
|
746
|
-
### Rate limiting
|
|
747
|
-
|
|
748
|
-
| Platform | Detection | Response |
|
|
749
|
-
|----------|-----------|----------|
|
|
750
|
-
| GitHub | 403 + `X-RateLimit-Remaining: 0` | Wait until `X-RateLimit-Reset`, retry once |
|
|
751
|
-
| Jira | 429 + `Retry-After` header | Wait the specified seconds, retry once |
|
|
752
|
-
| Linear | `RATELIMITED` error code | Wait 60s, retry once |
|
|
753
|
-
|
|
754
|
-
If retry also fails: stop, enter partial failure flow.
|
|
755
|
-
|
|
756
|
-
### Auth failure mid-batch
|
|
757
|
-
|
|
758
|
-
If a 401/403 occurs during ticket creation (token expired or revoked after preflight):
|
|
759
|
-
```
|
|
760
|
-
Auth failed during ticket creation. {M} of {N} created before failure.
|
|
761
|
-
Check credentials and re-run to resume.
|
|
762
|
-
```
|
|
763
|
-
Enter partial failure flow (Step 10).
|
|
764
|
-
|
|
765
|
-
### Timeout
|
|
766
|
-
|
|
767
|
-
| Platform | Timeout |
|
|
768
|
-
|----------|---------|
|
|
769
|
-
| GitHub | 15s per API call |
|
|
770
|
-
| Jira | 30s per API call |
|
|
771
|
-
| Linear | 30s per GraphQL call |
|
|
772
|
-
|
|
773
|
-
On timeout: `Request timed out. Ticket may have been created server-side. Check board before re-run.` Enter partial failure flow.
|
|
774
|
-
|
|
775
|
-
### Duplicate guard (on resume)
|
|
776
|
-
|
|
777
|
-
When resuming from a partial failure, before creating each ticket check the brief's decomposition table for an existing Ticket column entry. Additionally, scan the board for existing children of the parent with matching titles (case-insensitive exact match):
|
|
778
|
-
|
|
779
|
-
#### Jira
|
|
780
|
-
```bash
|
|
781
|
-
curl -s \
|
|
782
|
-
-u "$JIRA_USER:$JIRA_API_TOKEN" \
|
|
783
|
-
-H "Content-Type: application/json" \
|
|
784
|
-
-H "Accept: application/json" \
|
|
785
|
-
-X POST \
|
|
786
|
-
"$JIRA_BASE_URL/rest/api/3/search/jql" \
|
|
787
|
-
-d '{"jql": "parent = $PARENT_KEY", "maxResults": 50, "fields": ["summary"]}'
|
|
788
|
-
```
|
|
789
|
-
|
|
790
|
-
#### GitHub
|
|
791
|
-
```bash
|
|
792
|
-
curl -s \
|
|
793
|
-
-H "Authorization: Bearer $GITHUB_TOKEN" \
|
|
794
|
-
-H "Accept: application/vnd.github+json" \
|
|
795
|
-
-H "X-GitHub-Api-Version: 2022-11-28" \
|
|
796
|
-
"https://api.github.com/repos/$GITHUB_REPO/issues?state=open&per_page=30"
|
|
797
|
-
```
|
|
798
|
-
Filter for issues containing `Epic: #{parent_number}` in the body.
|
|
799
|
-
|
|
800
|
-
#### Linear
|
|
801
|
-
```graphql
|
|
802
|
-
query {
|
|
803
|
-
issues(filter: { identifier: { eq: "$PARENT_KEY" } }) {
|
|
804
|
-
nodes {
|
|
805
|
-
children { nodes { id identifier title } }
|
|
806
|
-
}
|
|
807
|
-
}
|
|
808
|
-
}
|
|
809
|
-
```
|
|
810
|
-
|
|
811
|
-
If matching children found:
|
|
812
|
-
```
|
|
813
|
-
{PARENT} already has children with similar titles:
|
|
814
|
-
- {KEY}: "{Title}" (matches proposed ticket #{N})
|
|
815
|
-
|
|
816
|
-
This brief may have already been approved. Continue anyway? [y/N]
|
|
817
|
-
```
|
|
818
|
-
Default: N (don't create duplicates).
|
|
819
|
-
|
|
820
|
-
---
|
|
821
|
-
|
|
822
|
-
## Notes
|
|
823
|
-
|
|
824
|
-
- The `Epic: {key}` convention is always the first line of every child ticket description across all platforms — this enables cross-platform epic completion detection by `fetchChildrenStatus`
|
|
825
|
-
- Mode labels (`clancy:afk` / `clancy:hitl`) are used by `/clancy:run` to decide whether to pick up a ticket autonomously or skip it for human attention
|
|
826
|
-
- Jira ADF construction: if complex content fails, wrap in a `codeBlock` node as fallback (matches the pattern used by `/clancy:approve-plan`)
|
|
827
|
-
- The `.approved` marker filename is the full brief filename with `.approved` appended (e.g. `.clancy/briefs/2026-03-14-auth-rework.md.approved`)
|
|
828
|
-
- Tickets are created sequentially (not in parallel) to maintain dependency ordering and respect rate limits
|
|
829
|
-
- The 500ms delay between ticket creations is sufficient for all platforms under normal rate limit conditions
|
|
830
|
-
- Dependency links use "Blocks" for Jira, `blockedBy` for Linear, and body text cross-references for GitHub
|
|
831
|
-
- Labels on Jira are auto-created by the platform; on GitHub they must be pre-created or the 422 fallback handles it; on Linear they are looked up and auto-created if missing
|
|
832
|
-
- Sprint/milestone assignment is deliberately not set — this is a team planning decision
|
|
833
|
-
- Linear `priority: 0` means "No priority" — the team triages after creation
|
|
834
|
-
- Jira priority is inherited from the parent if available; Linear and GitHub do not inherit priority
|