chief-clancy 0.7.3 → 0.8.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.
@@ -314,25 +314,29 @@ Could not update plan comment. The plan is still promoted to the description.
314
314
 
315
315
  ---
316
316
 
317
- ## Step 6 — Post-approval transition
317
+ ## Step 6 — Post-approval label transition
318
318
 
319
- Transition the ticket from the planning queue to the implementation queue. This is **best-effort** — warn on failure, continue.
319
+ Transition the ticket from the planning queue to the implementation queue via pipeline labels. This is **best-effort** — warn on failure, continue.
320
320
 
321
- ### GitHub (always, not configurable)
321
+ **Crash safety:** Add the new label BEFORE removing the old one. A ticket briefly has two labels (harmless) rather than zero labels (ticket lost).
322
322
 
323
- 1. **Remove planning label:**
323
+ **This label transition is mandatory — always apply and remove.** Use `CLANCY_LABEL_BUILD` from `.clancy/.env` if set, otherwise `clancy:build`. Use `CLANCY_LABEL_PLAN` from `.clancy/.env` if set, otherwise fall back to `CLANCY_PLAN_LABEL`, otherwise `clancy:plan`. Ensure the build label exists on the board (create if missing), add it to the ticket, then remove the plan label.
324
+
325
+ ### GitHub
326
+
327
+ 1. **Add build label** (ensure it exists first):
324
328
  ```bash
329
+ # Ensure label exists (ignore 422 = already exists)
325
330
  curl -s \
326
331
  -H "Authorization: Bearer $GITHUB_TOKEN" \
327
332
  -H "Accept: application/vnd.github+json" \
328
333
  -H "X-GitHub-Api-Version: 2022-11-28" \
329
- -X DELETE \
330
- "https://api.github.com/repos/$GITHUB_REPO/issues/$ISSUE_NUMBER/labels/$CLANCY_PLAN_LABEL"
331
- ```
332
- `CLANCY_PLAN_LABEL` defaults to `needs-refinement`. URL-encode the label name. Ignore 404 (label not on issue). If `CLANCY_PLAN_LABEL` is not set, use the default `needs-refinement`.
334
+ -H "Content-Type: application/json" \
335
+ -X POST \
336
+ "https://api.github.com/repos/$GITHUB_REPO/labels" \
337
+ -d '{"name": "$CLANCY_LABEL_BUILD", "color": "0075ca"}'
333
338
 
334
- 2. **Add implementation label** (only if `CLANCY_LABEL` is set — skip if unset):
335
- ```bash
339
+ # Add to issue
336
340
  curl -s \
337
341
  -H "Authorization: Bearer $GITHUB_TOKEN" \
338
342
  -H "Accept: application/vnd.github+json" \
@@ -340,38 +344,68 @@ Transition the ticket from the planning queue to the implementation queue. This
340
344
  -H "Content-Type: application/json" \
341
345
  -X POST \
342
346
  "https://api.github.com/repos/$GITHUB_REPO/issues/$ISSUE_NUMBER/labels" \
343
- -d '{"labels": ["$CLANCY_LABEL"]}'
347
+ -d '{"labels": ["$CLANCY_LABEL_BUILD"]}'
344
348
  ```
345
- If `CLANCY_LABEL` is not set, skip this step entirely (only the plan label removal is done). `CLANCY_LABEL` has no default — it is fully optional. When set (e.g. `clancy`), use its value. If the label does not exist on the repo, attempt to create it:
349
+
350
+ 2. **Remove plan label:**
346
351
  ```bash
347
352
  curl -s \
348
353
  -H "Authorization: Bearer $GITHUB_TOKEN" \
349
354
  -H "Accept: application/vnd.github+json" \
355
+ -H "X-GitHub-Api-Version: 2022-11-28" \
356
+ -X DELETE \
357
+ "https://api.github.com/repos/$GITHUB_REPO/issues/$ISSUE_NUMBER/labels/$(echo $CLANCY_LABEL_PLAN | jq -Rr @uri)"
358
+ ```
359
+ Ignore 404 (label not on issue).
360
+
361
+ ### Jira
362
+
363
+ 1. **Add build label** (Jira auto-creates labels):
364
+ ```bash
365
+ # Fetch current labels
366
+ CURRENT_LABELS=$(curl -s \
367
+ -u "$JIRA_USER:$JIRA_API_TOKEN" \
368
+ -H "Accept: application/json" \
369
+ "$JIRA_BASE_URL/rest/api/3/issue/$TICKET_KEY?fields=labels" | jq -r '.fields.labels')
370
+
371
+ # Add build label
372
+ UPDATED_LABELS=$(echo "$CURRENT_LABELS" | jq --arg build "$CLANCY_LABEL_BUILD" '. + [$build] | unique')
373
+
374
+ curl -s \
375
+ -u "$JIRA_USER:$JIRA_API_TOKEN" \
376
+ -X PUT \
350
377
  -H "Content-Type: application/json" \
351
- -X POST \
352
- "https://api.github.com/repos/$GITHUB_REPO/labels" \
353
- -d '{"name": "$CLANCY_LABEL", "color": "0075ca"}'
378
+ "$JIRA_BASE_URL/rest/api/3/issue/$TICKET_KEY" \
379
+ -d "{\"fields\": {\"labels\": $UPDATED_LABELS}}"
354
380
  ```
355
- If 403 (no admin access) or 422 (invalid name): warn, continue without label.
356
381
 
357
- ### Jira (only if `CLANCY_STATUS_PLANNED` is set)
382
+ 2. **Remove plan label:**
383
+ ```bash
384
+ # Re-fetch labels (may have changed), remove plan label
385
+ CURRENT_LABELS=$(curl -s \
386
+ -u "$JIRA_USER:$JIRA_API_TOKEN" \
387
+ -H "Accept: application/json" \
388
+ "$JIRA_BASE_URL/rest/api/3/issue/$TICKET_KEY?fields=labels" | jq -r '.fields.labels')
358
389
 
359
- If `CLANCY_STATUS_PLANNED` is not set: skip transition, no warning needed.
390
+ UPDATED_LABELS=$(echo "$CURRENT_LABELS" | jq --arg plan "$CLANCY_LABEL_PLAN" '[.[] | select(. != $plan)]')
360
391
 
361
- If `CLANCY_STATUS_PLANNED` is set:
392
+ curl -s \
393
+ -u "$JIRA_USER:$JIRA_API_TOKEN" \
394
+ -X PUT \
395
+ -H "Content-Type: application/json" \
396
+ "$JIRA_BASE_URL/rest/api/3/issue/$TICKET_KEY" \
397
+ -d "{\"fields\": {\"labels\": $UPDATED_LABELS}}"
398
+ ```
362
399
 
363
- 1. **Fetch transitions:**
400
+ 3. **Status transition** (only if `CLANCY_STATUS_PLANNED` is set — skip if unset):
364
401
  ```bash
402
+ # Fetch transitions
365
403
  curl -s \
366
404
  -u "$JIRA_USER:$JIRA_API_TOKEN" \
367
405
  -H "Accept: application/json" \
368
406
  "$JIRA_BASE_URL/rest/api/3/issue/$TICKET_KEY/transitions"
369
- ```
370
407
 
371
- 2. **Find matching transition:** Look for a transition where `.name` matches `CLANCY_STATUS_PLANNED` (case-insensitive). This matches the pattern used in the runtime Jira module.
372
-
373
- 3. **Execute transition:**
374
- ```bash
408
+ # Find matching transition and execute
375
409
  curl -s \
376
410
  -u "$JIRA_USER:$JIRA_API_TOKEN" \
377
411
  -X POST \
@@ -385,23 +419,50 @@ On failure:
385
419
  Could not transition ticket. Move it manually to your implementation queue.
386
420
  ```
387
421
 
388
- ### Linear (always)
422
+ ### Linear
423
+
424
+ 1. **Add build label** (ensure it exists, then add):
425
+ ```graphql
426
+ # Ensure label exists — check team labels, workspace labels, create if missing
427
+ mutation {
428
+ issueLabelCreate(input: {
429
+ teamId: "$LINEAR_TEAM_ID"
430
+ name: "$CLANCY_LABEL_BUILD"
431
+ color: "#0075ca"
432
+ }) { success issueLabel { id } }
433
+ }
434
+
435
+ # Fetch current label IDs on the issue, add build label ID
436
+ mutation {
437
+ issueUpdate(
438
+ id: "$ISSUE_UUID"
439
+ input: { labelIds: [...currentLabelIds, buildLabelId] }
440
+ ) { success }
441
+ }
442
+ ```
443
+
444
+ 2. **Remove plan label:**
445
+ ```graphql
446
+ # Fetch current label IDs, filter out plan label ID
447
+ mutation {
448
+ issueUpdate(
449
+ id: "$ISSUE_UUID"
450
+ input: { labelIds: [currentLabelIds without planLabelId] }
451
+ ) { success }
452
+ }
453
+ ```
389
454
 
390
- 1. **Resolve "unstarted" state:**
455
+ 3. **State transition** (always):
391
456
  ```graphql
457
+ # Resolve "unstarted" state
392
458
  query {
393
459
  workflowStates(filter: {
394
460
  team: { id: { eq: "$LINEAR_TEAM_ID" } }
395
461
  type: { eq: "unstarted" }
396
- }) {
397
- nodes { id name }
398
- }
462
+ }) { nodes { id name } }
399
463
  }
400
- ```
401
- Use `nodes[0].id` as the target state. If nodes is empty: warn `No 'unstarted' state found for team.` Skip transition.
402
464
 
403
- 2. **Transition:**
404
- ```graphql
465
+ # Transition
405
466
  mutation {
406
467
  issueUpdate(
407
468
  id: "$ISSUE_UUID"
@@ -409,6 +470,7 @@ Could not transition ticket. Move it manually to your implementation queue.
409
470
  ) { success }
410
471
  }
411
472
  ```
473
+ If no `unstarted` state found: warn, skip transition.
412
474
 
413
475
  On failure:
414
476
  ```
@@ -421,20 +483,13 @@ Could not transition ticket. Move it manually to your implementation queue.
421
483
 
422
484
  On success, display a board-specific message:
423
485
 
424
- **GitHub (with CLANCY_LABEL set):**
486
+ **GitHub:**
425
487
  ```
426
- Plan promoted. Label swapped: {plan_label} → {impl_label}. Ready for /clancy:once.
488
+ Plan promoted. Label swapped: {CLANCY_LABEL_PLAN} → {CLANCY_LABEL_BUILD}. Ready for /clancy:once.
427
489
 
428
490
  "Book 'em, Lou." — The ticket is ready for /clancy:once.
429
491
  ```
430
492
 
431
- **GitHub (without CLANCY_LABEL):**
432
- ```
433
- Plan promoted. Label removed: {plan_label}. Add your implementation label manually, or run /clancy:once.
434
-
435
- "Book 'em, Lou."
436
- ```
437
-
438
493
  **Jira (with transition):**
439
494
  ```
440
495
  Plan promoted. Ticket transitioned to {CLANCY_STATUS_PLANNED}.
@@ -136,9 +136,9 @@ Then skip to Step 3b with this single ticket.
136
136
  Build the JQL using planning-specific env vars:
137
137
  - `CLANCY_PLAN_STATUS` defaults to `Backlog` if not set
138
138
  - Sprint clause: include `AND sprint in openSprints()` if `CLANCY_JQL_SPRINT` is set
139
- - Label clause: include `AND labels = "$CLANCY_LABEL"` if `CLANCY_LABEL` is set
139
+ - Label clause: include `AND labels = "$CLANCY_LABEL_PLAN"` if `CLANCY_LABEL_PLAN` is set (falls back to `CLANCY_PLAN_LABEL` if `CLANCY_LABEL_PLAN` is not set). If neither is set, include `AND labels = "$CLANCY_LABEL"` if `CLANCY_LABEL` is set.
140
140
 
141
- Full JQL: `project=$JIRA_PROJECT_KEY [AND sprint in openSprints()] [AND labels = "$CLANCY_LABEL"] AND assignee=currentUser() AND status="$CLANCY_PLAN_STATUS" ORDER BY priority ASC`
141
+ Full JQL: `project=$JIRA_PROJECT_KEY [AND sprint in openSprints()] [AND labels = "$CLANCY_LABEL_PLAN"] AND assignee=currentUser() AND status="$CLANCY_PLAN_STATUS" ORDER BY priority ASC`
142
142
 
143
143
  ```bash
144
144
  RESPONSE=$(curl -s \
@@ -164,16 +164,16 @@ Then fetch issues:
164
164
  RESPONSE=$(curl -s \
165
165
  -H "Authorization: Bearer $GITHUB_TOKEN" \
166
166
  -H "X-GitHub-Api-Version: 2022-11-28" \
167
- "https://api.github.com/repos/$GITHUB_REPO/issues?state=open&assignee=$GITHUB_USERNAME&labels=$CLANCY_PLAN_LABEL&per_page=<N>")
167
+ "https://api.github.com/repos/$GITHUB_REPO/issues?state=open&assignee=$GITHUB_USERNAME&labels=$CLANCY_LABEL_PLAN&per_page=<N>")
168
168
  ```
169
169
 
170
- - `CLANCY_PLAN_LABEL` defaults to `needs-refinement` if not set
170
+ - `CLANCY_LABEL_PLAN` is the pipeline label for the planning queue (default: `clancy:plan`). Falls back to `CLANCY_PLAN_LABEL` if `CLANCY_LABEL_PLAN` is not set. If neither is set, defaults to `needs-refinement`.
171
171
  - Filter out PRs (entries with `pull_request` key)
172
172
  - For each issue, fetch comments: `GET /repos/$GITHUB_REPO/issues/{number}/comments`
173
173
 
174
174
  #### Linear
175
175
 
176
- Build the filter using `CLANCY_PLAN_STATE_TYPE` (defaults to `backlog` if not set):
176
+ Build the filter using `CLANCY_PLAN_STATE_TYPE` (defaults to `backlog` if not set). If `CLANCY_LABEL_PLAN` is set (falls back to `CLANCY_PLAN_LABEL`), add a label filter to the query:
177
177
 
178
178
  ```graphql
179
179
  query {
@@ -182,6 +182,7 @@ query {
182
182
  filter: {
183
183
  state: { type: { eq: "$CLANCY_PLAN_STATE_TYPE" } }
184
184
  team: { id: { eq: "$LINEAR_TEAM_ID" } }
185
+ labels: { name: { eq: "$CLANCY_LABEL_PLAN" } } # Only if CLANCY_LABEL_PLAN is set
185
186
  }
186
187
  first: $N
187
188
  orderBy: priority
@@ -214,7 +215,7 @@ If no tickets found:
214
215
 
215
216
  Then display board-specific guidance:
216
217
 
217
- - **GitHub:** `For GitHub: planning uses the "$CLANCY_PLAN_LABEL" label (default: needs-refinement), not "clancy". Apply that label to issues you want planned.`
218
+ - **GitHub:** `For GitHub: planning uses the "$CLANCY_LABEL_PLAN" label (default: clancy:plan, fallback: $CLANCY_PLAN_LABEL or needs-refinement). Apply that label to issues you want planned.`
218
219
  - **Jira:** `Check that CLANCY_PLAN_STATUS (currently: "$CLANCY_PLAN_STATUS") matches a status in your Jira project, and that tickets in that status are assigned to you.`
219
220
  - **Linear:** `Check that CLANCY_PLAN_STATE_TYPE (currently: "$CLANCY_PLAN_STATE_TYPE") is a valid Linear state type (backlog, unstarted, started, completed, canceled, triage), and that tickets in that state are assigned to you in team $LINEAR_TEAM_ID.`
220
221
 
@@ -10,7 +10,9 @@ Display the following:
10
10
 
11
11
  Named after Chief Clancy Wiggum (Ralph's dad, The Simpsons). Built on the Ralph technique
12
12
  coined by Geoffrey Huntley (ghuntley.com/ralph/). Clancy extends that foundation with board
13
- integration, structured codebase docs, and a git workflow built for team development.
13
+ integration (6 boards), structured codebase docs, and a git workflow built for team development.
14
+
15
+ **Supported boards:** Jira, GitHub Issues, Linear, Shortcut, Notion, Azure DevOps
14
16
 
15
17
  ### Planner *(optional — enable via `CLANCY_ROLES=planner` in `.clancy/.env`)*
16
18
 
@@ -87,11 +87,16 @@ Which Kanban board are you using?
87
87
  [1] Jira
88
88
  [2] GitHub Issues
89
89
  [3] Linear
90
- [4] My board isn't listed
90
+ [4] Shortcut
91
+ [5] Notion
92
+ [6] Azure DevOps
93
+ [7] My board isn't listed
91
94
 
92
- If the user selects [4], output the dead-end message and stop:
95
+ Auto-detection hint: silently check `.clancy/.env` for existing board env vars (`JIRA_BASE_URL`, `GITHUB_TOKEN`, `LINEAR_API_KEY`, `SHORTCUT_API_TOKEN`, `NOTION_DATABASE_ID`, `AZDO_ORG`). If detected, show: `Detected: {board} from your env vars. Use this? [Y/n]` — if yes, skip to Q2 for that board.
93
96
 
94
- Clancy currently supports Jira, GitHub Issues, and Linear out of the box.
97
+ If the user selects [7], output the dead-end message and stop:
98
+
99
+ Clancy currently supports Jira, GitHub Issues, Linear, Shortcut, Notion, and Azure DevOps out of the box.
95
100
 
96
101
  Your board isn't supported yet — but you can add it:
97
102
  · Open an issue: github.com/Pushedskydiver/clancy/issues
@@ -106,6 +111,32 @@ Do not scaffold anything after this message. Stop completely.
106
111
 
107
112
  ---
108
113
 
114
+ **Shortcut** — ask in this order:
115
+
116
+ 1. `Paste your Shortcut API token: (create one at app.shortcut.com/settings/account/api-tokens)`
117
+ 2. `What workflow should Clancy use? (press Enter to auto-detect)` — if blank, auto-detect the first workflow via `GET /api/v3/workflows`
118
+
119
+ Store as `SHORTCUT_API_TOKEN` and optionally `SHORTCUT_WORKFLOW` in `.clancy/.env`.
120
+
121
+ **Notion** — ask in this order:
122
+
123
+ 1. `Paste your Notion integration token: (create one at notion.so/my-integrations)`
124
+ 2. `What's your Notion database ID? (the 32-character hex string in your database URL)`
125
+ 3. `What property name represents the ticket status? [Status]`
126
+ 4. `What property name represents the assignee? [Assignee]`
127
+
128
+ Store as `NOTION_TOKEN`, `NOTION_DATABASE_ID`, and optionally `CLANCY_NOTION_STATUS` and `CLANCY_NOTION_ASSIGNEE` in `.clancy/.env`.
129
+
130
+ **Azure DevOps** — ask in this order:
131
+
132
+ 1. `What's your Azure DevOps organisation name? (e.g. your-org)`
133
+ 2. `What's your Azure DevOps project name?`
134
+ 3. `Paste your Azure DevOps personal access token: (needs Work Items Read & Write scope)`
135
+
136
+ Store as `AZDO_ORG`, `AZDO_PROJECT`, and `AZDO_PAT` in `.clancy/.env`.
137
+
138
+ ---
139
+
109
140
  ### Q2: Board-specific config
110
141
 
111
142
  Ask each question individually and wait for an answer before moving to the next.
@@ -497,6 +528,47 @@ If `n` or `N`: store `CLANCY_BRANCH_GUARD=false` in `.clancy/.env`.
497
528
 
498
529
  ---
499
530
 
531
+ ### Q3i (all boards): Quiet hours
532
+
533
+ Output:
534
+
535
+ ```
536
+ Pause AFK runs during specific hours? (e.g. business hours, overnight)
537
+
538
+ [1] Skip — no quiet hours
539
+ [2] Set quiet hours
540
+ ```
541
+
542
+ If [1] or enter: skip — no `CLANCY_QUIET_START` or `CLANCY_QUIET_END` written.
543
+ If [2]: ask:
544
+
545
+ ```
546
+ Quiet start time (HH:MM, 24h format, e.g. 22:00):
547
+ ```
548
+
549
+ Then:
550
+
551
+ ```
552
+ Quiet end time (HH:MM, 24h format, e.g. 06:00):
553
+ ```
554
+
555
+ Store as `CLANCY_QUIET_START` and `CLANCY_QUIET_END` in `.clancy/.env`.
556
+
557
+ ---
558
+
559
+ ### Q3j (all boards): Desktop notifications
560
+
561
+ Output:
562
+
563
+ ```
564
+ Send desktop notifications when tickets complete or errors occur? [Y/n]
565
+ ```
566
+
567
+ If yes or enter: store `CLANCY_DESKTOP_NOTIFY=true` in `.clancy/.env`.
568
+ If no: store `CLANCY_DESKTOP_NOTIFY=false` in `.clancy/.env`.
569
+
570
+ ---
571
+
500
572
  ### Q4: Base branch (auto-detect)
501
573
 
502
574
  Silently detect the base branch — do not ask unless detection fails:
@@ -592,6 +664,74 @@ Note: as more roles are added in future versions, they appear as additional numb
592
664
 
593
665
  ---
594
666
 
667
+ ## Step 4c-2 — Pipeline labels (conditional)
668
+
669
+ Only ask this if any optional role was enabled in Step 4c. If neither Planner nor Strategist was selected, skip this section entirely. If `CLANCY_LABEL` or `CLANCY_PLAN_LABEL` are already set in `.clancy/.env`, show:
670
+
671
+ ```
672
+ Note: CLANCY_LABEL and CLANCY_PLAN_LABEL are deprecated.
673
+ Use CLANCY_LABEL_BUILD and CLANCY_LABEL_PLAN instead.
674
+ Your existing values will continue to work as fallbacks.
675
+ ```
676
+
677
+ **If the user enabled Strategist (or both Strategist + Planner):**
678
+
679
+ Output:
680
+
681
+ ```
682
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
683
+ Pipeline Labels
684
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
685
+
686
+ Clancy uses labels to move tickets through pipeline stages:
687
+ brief → plan → build
688
+
689
+ Each label marks which queue a ticket belongs to.
690
+ ```
691
+
692
+ Then ask each label in order:
693
+
694
+ ```
695
+ What label marks tickets that have been briefed (awaiting approval)?
696
+ [clancy:brief]
697
+ ```
698
+
699
+ If a value is entered: store as `CLANCY_LABEL_BRIEF` in `.clancy/.env`. Wrap in double quotes.
700
+ If enter is pressed: use default — store `CLANCY_LABEL_BRIEF="clancy:brief"` in `.clancy/.env`.
701
+
702
+ ```
703
+ What label marks tickets that need planning?
704
+ [clancy:plan]
705
+ ```
706
+
707
+ If a value is entered: store as `CLANCY_LABEL_PLAN` in `.clancy/.env`. Wrap in double quotes.
708
+ If enter is pressed: use default — store `CLANCY_LABEL_PLAN="clancy:plan"` in `.clancy/.env`.
709
+
710
+ ```
711
+ What label marks tickets ready to build?
712
+ [clancy:build]
713
+ ```
714
+
715
+ If a value is entered: store as `CLANCY_LABEL_BUILD` in `.clancy/.env`. Wrap in double quotes.
716
+ If enter is pressed: use default — store `CLANCY_LABEL_BUILD="clancy:build"` in `.clancy/.env`.
717
+
718
+ **If the user enabled Planner only (no Strategist):**
719
+
720
+ Skip `CLANCY_LABEL_BRIEF` (no `/clancy:brief` command). Ask only:
721
+
722
+ ```
723
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
724
+ Pipeline Labels
725
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
726
+
727
+ Clancy uses labels to move tickets through pipeline stages:
728
+ plan → build
729
+ ```
730
+
731
+ Then ask `CLANCY_LABEL_PLAN` and `CLANCY_LABEL_BUILD` using the same prompts and defaults as above.
732
+
733
+ ---
734
+
595
735
  ## Step 4d (if Planner role selected): Planning queue config
596
736
 
597
737
  Only ask this if the user selected Planner in Step 4c above (or if re-running init and `CLANCY_ROLES` already includes `planner`).