@chief-clancy/terminal 0.1.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.
Files changed (217) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +62 -0
  3. package/dist/hooks/clancy-branch-guard.js +1 -0
  4. package/dist/hooks/clancy-check-update.js +2 -0
  5. package/dist/hooks/clancy-context-monitor.js +9 -0
  6. package/dist/hooks/clancy-credential-guard.js +2 -0
  7. package/dist/hooks/clancy-drift-detector.js +1 -0
  8. package/dist/hooks/clancy-notification.js +1 -0
  9. package/dist/hooks/clancy-post-compact.js +2 -0
  10. package/dist/hooks/clancy-statusline.js +1 -0
  11. package/dist/index.d.ts +24 -0
  12. package/dist/index.d.ts.map +1 -0
  13. package/dist/index.js +23 -0
  14. package/dist/index.js.map +1 -0
  15. package/dist/installer/file-ops/file-ops.d.ts +35 -0
  16. package/dist/installer/file-ops/file-ops.d.ts.map +1 -0
  17. package/dist/installer/file-ops/file-ops.js +95 -0
  18. package/dist/installer/file-ops/file-ops.js.map +1 -0
  19. package/dist/installer/file-ops/index.d.ts +2 -0
  20. package/dist/installer/file-ops/index.d.ts.map +1 -0
  21. package/dist/installer/file-ops/index.js +2 -0
  22. package/dist/installer/file-ops/index.js.map +1 -0
  23. package/dist/installer/hook-installer/hook-installer.d.ts +22 -0
  24. package/dist/installer/hook-installer/hook-installer.d.ts.map +1 -0
  25. package/dist/installer/hook-installer/hook-installer.js +213 -0
  26. package/dist/installer/hook-installer/hook-installer.js.map +1 -0
  27. package/dist/installer/hook-installer/index.d.ts +2 -0
  28. package/dist/installer/hook-installer/index.d.ts.map +1 -0
  29. package/dist/installer/hook-installer/index.js +2 -0
  30. package/dist/installer/hook-installer/index.js.map +1 -0
  31. package/dist/installer/install/index.d.ts +3 -0
  32. package/dist/installer/install/index.d.ts.map +1 -0
  33. package/dist/installer/install/index.js +2 -0
  34. package/dist/installer/install/index.js.map +1 -0
  35. package/dist/installer/install/install.d.ts +124 -0
  36. package/dist/installer/install/install.d.ts.map +1 -0
  37. package/dist/installer/install/install.js +255 -0
  38. package/dist/installer/install/install.js.map +1 -0
  39. package/dist/installer/manifest/index.d.ts +2 -0
  40. package/dist/installer/manifest/index.d.ts.map +1 -0
  41. package/dist/installer/manifest/index.js +2 -0
  42. package/dist/installer/manifest/index.js.map +1 -0
  43. package/dist/installer/manifest/manifest.d.ts +46 -0
  44. package/dist/installer/manifest/manifest.d.ts.map +1 -0
  45. package/dist/installer/manifest/manifest.js +180 -0
  46. package/dist/installer/manifest/manifest.js.map +1 -0
  47. package/dist/installer/prompts/index.d.ts +2 -0
  48. package/dist/installer/prompts/index.d.ts.map +1 -0
  49. package/dist/installer/prompts/index.js +2 -0
  50. package/dist/installer/prompts/index.js.map +1 -0
  51. package/dist/installer/prompts/prompts.d.ts +34 -0
  52. package/dist/installer/prompts/prompts.d.ts.map +1 -0
  53. package/dist/installer/prompts/prompts.js +28 -0
  54. package/dist/installer/prompts/prompts.js.map +1 -0
  55. package/dist/installer/role-filter/index.d.ts +2 -0
  56. package/dist/installer/role-filter/index.d.ts.map +1 -0
  57. package/dist/installer/role-filter/index.js +2 -0
  58. package/dist/installer/role-filter/index.js.map +1 -0
  59. package/dist/installer/role-filter/role-filter.d.ts +33 -0
  60. package/dist/installer/role-filter/role-filter.d.ts.map +1 -0
  61. package/dist/installer/role-filter/role-filter.js +91 -0
  62. package/dist/installer/role-filter/role-filter.js.map +1 -0
  63. package/dist/installer/shared/fs-errors/fs-errors.d.ts +3 -0
  64. package/dist/installer/shared/fs-errors/fs-errors.d.ts.map +1 -0
  65. package/dist/installer/shared/fs-errors/fs-errors.js +7 -0
  66. package/dist/installer/shared/fs-errors/fs-errors.js.map +1 -0
  67. package/dist/installer/shared/fs-errors/index.d.ts +2 -0
  68. package/dist/installer/shared/fs-errors/index.d.ts.map +1 -0
  69. package/dist/installer/shared/fs-errors/index.js +2 -0
  70. package/dist/installer/shared/fs-errors/index.js.map +1 -0
  71. package/dist/installer/shared/fs-guards/fs-guards.d.ts +3 -0
  72. package/dist/installer/shared/fs-guards/fs-guards.d.ts.map +1 -0
  73. package/dist/installer/shared/fs-guards/fs-guards.js +18 -0
  74. package/dist/installer/shared/fs-guards/fs-guards.js.map +1 -0
  75. package/dist/installer/shared/fs-guards/index.d.ts +2 -0
  76. package/dist/installer/shared/fs-guards/index.d.ts.map +1 -0
  77. package/dist/installer/shared/fs-guards/index.js +2 -0
  78. package/dist/installer/shared/fs-guards/index.js.map +1 -0
  79. package/dist/installer/shared/type-guards/index.d.ts +2 -0
  80. package/dist/installer/shared/type-guards/index.d.ts.map +1 -0
  81. package/dist/installer/shared/type-guards/index.js +2 -0
  82. package/dist/installer/shared/type-guards/index.js.map +1 -0
  83. package/dist/installer/shared/type-guards/type-guards.d.ts +8 -0
  84. package/dist/installer/shared/type-guards/type-guards.d.ts.map +1 -0
  85. package/dist/installer/shared/type-guards/type-guards.js +10 -0
  86. package/dist/installer/shared/type-guards/type-guards.js.map +1 -0
  87. package/dist/installer/ui/index.d.ts +2 -0
  88. package/dist/installer/ui/index.d.ts.map +1 -0
  89. package/dist/installer/ui/index.js +2 -0
  90. package/dist/installer/ui/index.js.map +1 -0
  91. package/dist/installer/ui/ui.d.ts +23 -0
  92. package/dist/installer/ui/ui.d.ts.map +1 -0
  93. package/dist/installer/ui/ui.js +121 -0
  94. package/dist/installer/ui/ui.js.map +1 -0
  95. package/dist/runner/autopilot/autopilot.d.ts +71 -0
  96. package/dist/runner/autopilot/autopilot.d.ts.map +1 -0
  97. package/dist/runner/autopilot/autopilot.js +206 -0
  98. package/dist/runner/autopilot/autopilot.js.map +1 -0
  99. package/dist/runner/autopilot/index.d.ts +2 -0
  100. package/dist/runner/autopilot/index.d.ts.map +1 -0
  101. package/dist/runner/autopilot/index.js +2 -0
  102. package/dist/runner/autopilot/index.js.map +1 -0
  103. package/dist/runner/cli-bridge/cli-bridge.d.ts +34 -0
  104. package/dist/runner/cli-bridge/cli-bridge.d.ts.map +1 -0
  105. package/dist/runner/cli-bridge/cli-bridge.js +53 -0
  106. package/dist/runner/cli-bridge/cli-bridge.js.map +1 -0
  107. package/dist/runner/cli-bridge/index.d.ts +2 -0
  108. package/dist/runner/cli-bridge/index.d.ts.map +1 -0
  109. package/dist/runner/cli-bridge/index.js +2 -0
  110. package/dist/runner/cli-bridge/index.js.map +1 -0
  111. package/dist/runner/dep-factory/deliver-phase.d.ts +24 -0
  112. package/dist/runner/dep-factory/deliver-phase.d.ts.map +1 -0
  113. package/dist/runner/dep-factory/deliver-phase.js +57 -0
  114. package/dist/runner/dep-factory/deliver-phase.js.map +1 -0
  115. package/dist/runner/dep-factory/dep-factory.d.ts +38 -0
  116. package/dist/runner/dep-factory/dep-factory.d.ts.map +1 -0
  117. package/dist/runner/dep-factory/dep-factory.js +193 -0
  118. package/dist/runner/dep-factory/dep-factory.js.map +1 -0
  119. package/dist/runner/dep-factory/index.d.ts +2 -0
  120. package/dist/runner/dep-factory/index.d.ts.map +1 -0
  121. package/dist/runner/dep-factory/index.js +2 -0
  122. package/dist/runner/dep-factory/index.js.map +1 -0
  123. package/dist/runner/dep-factory/invoke-phase.d.ts +20 -0
  124. package/dist/runner/dep-factory/invoke-phase.d.ts.map +1 -0
  125. package/dist/runner/dep-factory/invoke-phase.js +45 -0
  126. package/dist/runner/dep-factory/invoke-phase.js.map +1 -0
  127. package/dist/runner/implement/implement.d.ts +38 -0
  128. package/dist/runner/implement/implement.d.ts.map +1 -0
  129. package/dist/runner/implement/implement.js +61 -0
  130. package/dist/runner/implement/implement.js.map +1 -0
  131. package/dist/runner/implement/index.d.ts +2 -0
  132. package/dist/runner/implement/index.d.ts.map +1 -0
  133. package/dist/runner/implement/index.js +2 -0
  134. package/dist/runner/implement/index.js.map +1 -0
  135. package/dist/runner/notify/index.d.ts +2 -0
  136. package/dist/runner/notify/index.d.ts.map +1 -0
  137. package/dist/runner/notify/index.js +2 -0
  138. package/dist/runner/notify/index.js.map +1 -0
  139. package/dist/runner/notify/notify.d.ts +49 -0
  140. package/dist/runner/notify/notify.d.ts.map +1 -0
  141. package/dist/runner/notify/notify.js +90 -0
  142. package/dist/runner/notify/notify.js.map +1 -0
  143. package/dist/runner/prompt-builder/index.d.ts +2 -0
  144. package/dist/runner/prompt-builder/index.d.ts.map +1 -0
  145. package/dist/runner/prompt-builder/index.js +2 -0
  146. package/dist/runner/prompt-builder/index.js.map +1 -0
  147. package/dist/runner/prompt-builder/prompt-builder.d.ts +53 -0
  148. package/dist/runner/prompt-builder/prompt-builder.d.ts.map +1 -0
  149. package/dist/runner/prompt-builder/prompt-builder.js +122 -0
  150. package/dist/runner/prompt-builder/prompt-builder.js.map +1 -0
  151. package/dist/runner/session-report/index.d.ts +2 -0
  152. package/dist/runner/session-report/index.d.ts.map +1 -0
  153. package/dist/runner/session-report/index.js +2 -0
  154. package/dist/runner/session-report/index.js.map +1 -0
  155. package/dist/runner/session-report/session-report.d.ts +81 -0
  156. package/dist/runner/session-report/session-report.d.ts.map +1 -0
  157. package/dist/runner/session-report/session-report.js +227 -0
  158. package/dist/runner/session-report/session-report.js.map +1 -0
  159. package/dist/runner/shared/types.d.ts +30 -0
  160. package/dist/runner/shared/types.d.ts.map +1 -0
  161. package/dist/runner/shared/types.js +2 -0
  162. package/dist/runner/shared/types.js.map +1 -0
  163. package/dist/shared/ansi/ansi.d.ts +59 -0
  164. package/dist/shared/ansi/ansi.d.ts.map +1 -0
  165. package/dist/shared/ansi/ansi.js +59 -0
  166. package/dist/shared/ansi/ansi.js.map +1 -0
  167. package/dist/shared/ansi/index.d.ts +2 -0
  168. package/dist/shared/ansi/index.d.ts.map +1 -0
  169. package/dist/shared/ansi/index.js +2 -0
  170. package/dist/shared/ansi/index.js.map +1 -0
  171. package/package.json +52 -0
  172. package/src/agents/agents.test.ts +57 -0
  173. package/src/agents/arch-agent.md +80 -0
  174. package/src/agents/concerns-agent.md +96 -0
  175. package/src/agents/design-agent.md +146 -0
  176. package/src/agents/devils-advocate.md +54 -0
  177. package/src/agents/quality-agent.md +178 -0
  178. package/src/agents/tech-agent.md +101 -0
  179. package/src/agents/verification-gate.md +128 -0
  180. package/src/roles/implementer/commands/autopilot.md +11 -0
  181. package/src/roles/implementer/commands/dry-run.md +15 -0
  182. package/src/roles/implementer/commands/implement.md +19 -0
  183. package/src/roles/implementer/workflows/autopilot.md +136 -0
  184. package/src/roles/implementer/workflows/implement.md +161 -0
  185. package/src/roles/planner/commands/approve-plan.md +11 -0
  186. package/src/roles/planner/commands/plan.md +22 -0
  187. package/src/roles/planner/workflows/approve-plan.md +970 -0
  188. package/src/roles/planner/workflows/plan.md +868 -0
  189. package/src/roles/reviewer/commands/logs.md +7 -0
  190. package/src/roles/reviewer/commands/review.md +9 -0
  191. package/src/roles/reviewer/commands/status.md +9 -0
  192. package/src/roles/reviewer/workflows/logs.md +109 -0
  193. package/src/roles/reviewer/workflows/review.md +197 -0
  194. package/src/roles/reviewer/workflows/status.md +142 -0
  195. package/src/roles/roles.test.ts +87 -0
  196. package/src/roles/setup/commands/doctor.md +7 -0
  197. package/src/roles/setup/commands/help.md +80 -0
  198. package/src/roles/setup/commands/init.md +7 -0
  199. package/src/roles/setup/commands/map-codebase.md +17 -0
  200. package/src/roles/setup/commands/settings.md +7 -0
  201. package/src/roles/setup/commands/uninstall.md +5 -0
  202. package/src/roles/setup/commands/update-docs.md +9 -0
  203. package/src/roles/setup/commands/update.md +13 -0
  204. package/src/roles/setup/workflows/doctor.md +131 -0
  205. package/src/roles/setup/workflows/init.md +1096 -0
  206. package/src/roles/setup/workflows/map-codebase.md +130 -0
  207. package/src/roles/setup/workflows/scaffold.md +872 -0
  208. package/src/roles/setup/workflows/settings.md +958 -0
  209. package/src/roles/setup/workflows/uninstall.md +170 -0
  210. package/src/roles/setup/workflows/update-docs.md +95 -0
  211. package/src/roles/setup/workflows/update.md +287 -0
  212. package/src/roles/strategist/commands/approve-brief.md +23 -0
  213. package/src/roles/strategist/commands/brief.md +29 -0
  214. package/src/roles/strategist/workflows/approve-brief.md +1540 -0
  215. package/src/roles/strategist/workflows/brief.md +1330 -0
  216. package/src/templates/CLAUDE.md +101 -0
  217. package/src/templates/templates.test.ts +53 -0
@@ -0,0 +1,1330 @@
1
+ # Clancy Brief Workflow
2
+
3
+ ## Overview
4
+
5
+ Research an idea, interrogate it thoroughly, and generate a structured strategic brief with vertical-slice ticket decomposition. Briefs are saved locally and optionally posted as comments on the source ticket. Does not create tickets — that is `/clancy:approve-brief`.
6
+
7
+ ---
8
+
9
+ ## Step 1 — Preflight checks
10
+
11
+ 1. Check `.clancy/` exists and `.clancy/.env` is present. If not:
12
+
13
+ ```
14
+ .clancy/ not found. Run /clancy:init to set up Clancy first.
15
+ ```
16
+
17
+ Stop.
18
+
19
+ 2. Source `.clancy/.env` and check board credentials are present.
20
+
21
+ 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`:
22
+
23
+ ```
24
+ The Strategist role is not enabled. Add "strategist" to CLANCY_ROLES in .clancy/.env or run /clancy:settings.
25
+ ```
26
+
27
+ Stop.
28
+
29
+ 4. Branch freshness check — run `git fetch origin` and compare the current HEAD with `origin/$CLANCY_BASE_BRANCH` (defaults to `main`). If the local branch is behind:
30
+
31
+ **AFK mode** (`--afk` flag or `CLANCY_MODE=afk`): auto-pull without prompting. Run `git pull origin $CLANCY_BASE_BRANCH` and continue.
32
+
33
+ **Interactive mode:**
34
+
35
+ ```
36
+ ⚠️ Your local branch is behind origin/{CLANCY_BASE_BRANCH} by {N} commit(s).
37
+
38
+ [1] Pull latest
39
+ [2] Continue anyway
40
+ [3] Abort
41
+ ```
42
+
43
+ - [1] runs `git pull origin $CLANCY_BASE_BRANCH` and continues
44
+ - [2] continues without pulling
45
+ - [3] stops
46
+
47
+ ---
48
+
49
+ ## Step 2 — Parse arguments
50
+
51
+ Parse the arguments passed to the command. Arguments can appear in any order.
52
+
53
+ ### Flags
54
+
55
+ - **`--list`** — show brief inventory and stop (no brief generated)
56
+ - **`--fresh`** — discard any existing brief and start over from scratch
57
+ - **`--research`** — force web research agent (adds 1 web agent to the research phase)
58
+ - **`--afk`** — use AI-grill instead of human grill (no interactive questions)
59
+ - **`--epic {KEY}`** — hint for `/clancy:approve-brief` later. Stored in the brief's metadata. Ignored if the input is a board ticket (the source ticket is the parent).
60
+
61
+ ### Input modes
62
+
63
+ - **No input (no flags that consume arguments):** Interactive mode — but first check for `--afk`:
64
+ - If running in AFK mode (`--afk` flag OR `CLANCY_MODE=afk`): there is no human to answer. Display: `✗ Cannot run /clancy:brief in AFK mode without a ticket or idea. Use: /clancy:brief --afk #42 (GitHub) or PROJ-123 (Jira) or ENG-42 (Linear) or SC-123 (Shortcut) or 42 (Azure DevOps), or notion-XXXXXXXX (Notion), or /clancy:brief --afk "Add dark mode", or /clancy:brief 3 (batch mode — implies --afk).` Stop.
65
+ - Otherwise: prompt `What's the idea?` and parse the response. If the response looks like a ticket reference (`#42`, `PROJ-123`, `ENG-42`, `SC-123`, bare number on AzDo/Shortcut, or UUID/`notion-XXXXXXXX`), switch to board ticket mode. Otherwise treat as inline text.
66
+ - **Ticket key** (`PROJ-123`, `#42`, `ENG-42`, `42`): Board ticket mode — fetch the ticket from the board API. Validate format per platform:
67
+ - `#N` — valid for GitHub only. If board is Jira, Linear, or Shortcut: `The #N format is for GitHub Issues. Use a ticket key like PROJ-123.` Stop. If board is Azure DevOps: `The #N format is for GitHub Issues. Use a numeric work item ID (e.g. 42).` Stop. If board is Notion: `The #N format is for GitHub Issues. Use a Notion page UUID or notion-XXXXXXXX key.` Stop.
68
+ - `PROJ-123` / `ENG-42` (letters-dash-number) — valid for Jira, Linear, and Shortcut. If board is GitHub: `Use #N format for GitHub Issues (e.g. #42).` Stop. If board is Azure DevOps: `Use a numeric work item ID for Azure DevOps (e.g. 42).` Stop. If board is Notion: `Use a Notion page UUID or notion-XXXXXXXX key.` Stop.
69
+ - **Quoted string or unquoted non-matching text** (e.g. `"Add dark mode"`): Inline text mode — use the text directly as the idea.
70
+ - **`--from {path}`** — From file mode. Cannot be combined with a ticket reference (error if both present: `Cannot use both a ticket reference and --from. Use one or the other.`). Validate:
71
+ - File does not exist: `File not found: {path}` Stop.
72
+ - File is empty: `File is empty: {path}` Stop.
73
+ - File > 50KB: Warn `Large file ({size}KB). Clancy will use the first ~50KB for context.` Truncate internally, continue.
74
+ - **Bare positive integer** (e.g. `/clancy:brief 3`): Batch mode or ambiguous.
75
+ - Board is GitHub and value could be an issue: Ambiguous — ask: `Did you mean issue #3 or batch 3 tickets? [1] Brief issue #3 [2] Brief 3 tickets from queue`
76
+ - Board is Azure DevOps and value > 10: treat as a work item ID (no ambiguity — AzDo always uses numeric IDs)
77
+ - Board is Jira, Linear, Shortcut, or Notion: Batch mode (N tickets from queue). Implies `--afk` (AI-grill for all).
78
+
79
+ If N > 10: `Maximum batch size is 10. Briefing 10 tickets.`
80
+
81
+ ### --list flag handling
82
+
83
+ If `--list` is present (with or without other arguments), jump to Step 11 (Brief Inventory) and stop.
84
+
85
+ ---
86
+
87
+ ## Step 3 — Gather idea (mode-specific)
88
+
89
+ ### Board ticket mode
90
+
91
+ Fetch the source ticket from the board API.
92
+
93
+ #### GitHub — Fetch specific issue
94
+
95
+ ```bash
96
+ RESPONSE=$(curl -s \
97
+ -H "Authorization: Bearer $GITHUB_TOKEN" \
98
+ -H "X-GitHub-Api-Version: 2022-11-28" \
99
+ "https://api.github.com/repos/$GITHUB_REPO/issues/$ISSUE_NUMBER")
100
+ ```
101
+
102
+ Validate the response:
103
+
104
+ - If `pull_request` field is present and non-null: `#N is a pull request, not an issue.` Stop.
105
+ - If `state` is `closed`: warn `#N is closed. Brief it anyway? [y/N]`
106
+ - If `body` is null/empty: warn `No issue description — briefing from title only.`
107
+ - Extract: `title`, `body`, `labels`, `milestone`.
108
+
109
+ Fetch comments for existing brief detection:
110
+
111
+ ```bash
112
+ COMMENTS=$(curl -s \
113
+ -H "Authorization: Bearer $GITHUB_TOKEN" \
114
+ -H "X-GitHub-Api-Version: 2022-11-28" \
115
+ "https://api.github.com/repos/$GITHUB_REPO/issues/$ISSUE_NUMBER/comments?per_page=100")
116
+ ```
117
+
118
+ #### Jira — Fetch specific ticket
119
+
120
+ ```bash
121
+ RESPONSE=$(curl -s \
122
+ -u "$JIRA_USER:$JIRA_API_TOKEN" \
123
+ -H "Accept: application/json" \
124
+ "$JIRA_BASE_URL/rest/api/3/issue/$TICKET_KEY?fields=summary,description,status,issuetype,parent,customfield_10014,components,priority,comment,project")
125
+ ```
126
+
127
+ Validate the response:
128
+
129
+ - If `fields.status.statusCategory.key` is `done`: warn `{KEY} is Done. Brief it anyway? [y/N]`
130
+ - If `fields.status.statusCategory.key` is `indeterminate`: warn `{KEY} is In Progress — briefing anyway.`
131
+ - If `fields.issuetype.name` is `Epic`: note `{KEY} is an Epic — child tickets will be created under it.`
132
+ - Extract: `summary`, `description` (ADF → plain text via `extractAdfText()`), status, existing comments from `comment.comments[]`.
133
+
134
+ #### Linear — Fetch specific issue
135
+
136
+ ```graphql
137
+ query {
138
+ issues(filter: { identifier: { eq: "$IDENTIFIER" } }) {
139
+ nodes {
140
+ id
141
+ identifier
142
+ title
143
+ description
144
+ state {
145
+ id
146
+ name
147
+ type
148
+ }
149
+ parent {
150
+ id
151
+ identifier
152
+ title
153
+ }
154
+ children {
155
+ nodes {
156
+ id
157
+ identifier
158
+ title
159
+ state {
160
+ type
161
+ }
162
+ }
163
+ }
164
+ team {
165
+ id
166
+ key
167
+ name
168
+ }
169
+ labels {
170
+ nodes {
171
+ id
172
+ name
173
+ }
174
+ }
175
+ priority
176
+ estimate
177
+ }
178
+ }
179
+ }
180
+ ```
181
+
182
+ Validate the response:
183
+
184
+ - If `nodes` is empty: `Issue {KEY} not found on Linear.` Stop.
185
+ - If `state.type` is `completed` or `canceled`: warn `{KEY} is {state.name}. Brief it anyway? [y/N]`
186
+ - If `state.type` is `started`: warn `{KEY} is In Progress — briefing anyway.`
187
+ - If `parent` is present: warn `{KEY} is a sub-issue of {parent.identifier}. Creating children will produce a 3-level hierarchy. Continue? [Y/n]`
188
+ - If `team.id` differs from `LINEAR_TEAM_ID`: warn `{KEY} belongs to team "{team.name}", but LINEAR_TEAM_ID is different. Continue? [Y/n]`
189
+
190
+ #### Shortcut — Fetch specific story
191
+
192
+ ```bash
193
+ RESPONSE=$(curl -s \
194
+ -H "Shortcut-Token: $SHORTCUT_API_TOKEN" \
195
+ -H "Content-Type: application/json" \
196
+ "https://api.app.shortcut.com/api/v3/stories/$STORY_ID")
197
+ ```
198
+
199
+ Extract the story ID from the key (e.g. `SC-123` → `123`, or use the numeric portion). Validate the response:
200
+
201
+ - If response status is 404: `Story ${KEY} not found on Shortcut.` Stop.
202
+ - If `completed` is `true` or `archived` is `true`: warn `Story is completed/archived. Brief it anyway? [y/N]`
203
+ - If `started` is `true`: warn `Story is in progress — briefing anyway.`
204
+ - If `epic_id` is present: note `Story belongs to epic — child tickets will be created under it.`
205
+
206
+ Fetch comments for existing brief detection:
207
+
208
+ ```bash
209
+ COMMENTS=$(curl -s \
210
+ -H "Shortcut-Token: $SHORTCUT_API_TOKEN" \
211
+ "https://api.app.shortcut.com/api/v3/stories/$STORY_ID/comments")
212
+ ```
213
+
214
+ Map fields: title = `name`, description = `description` (markdown), parent = `epic_id` (fetch epic name via `GET /api/v3/epics/$EPIC_ID` if set), labels = `labels[].name`.
215
+
216
+ #### Azure DevOps — Fetch specific work item
217
+
218
+ ```bash
219
+ RESPONSE=$(curl -s \
220
+ -u ":$AZDO_PAT" \
221
+ -H "Accept: application/json" \
222
+ "https://dev.azure.com/$AZDO_ORG/$AZDO_PROJECT/_apis/wit/workitems/$WORK_ITEM_ID?\$expand=relations&api-version=7.1")
223
+ ```
224
+
225
+ Validate the response:
226
+
227
+ - If response contains `"message"` with `"does not exist"`: `Work item ${ID} not found.` Stop.
228
+ - If `fields["System.State"]` is a done/resolved state (e.g. `Done`, `Closed`, `Resolved`): warn `Work item is done. Brief it anyway? [y/N]`
229
+ - If `fields["System.State"]` is `Active`: warn `Work item is Active — briefing anyway.`
230
+ - Extract: `fields["System.Title"]`, `fields["System.Description"]` (HTML — strip tags), `fields["System.State"]`, `fields["System.Tags"]`.
231
+
232
+ Fetch comments (separate endpoint):
233
+
234
+ ```bash
235
+ COMMENTS=$(curl -s \
236
+ -u ":$AZDO_PAT" \
237
+ -H "Accept: application/json" \
238
+ "https://dev.azure.com/$AZDO_ORG/$AZDO_PROJECT/_apis/wit/workitems/$WORK_ITEM_ID/comments?api-version=7.1-preview.4")
239
+ ```
240
+
241
+ Check `relations` array for parent (type `System.LinkTypes.Hierarchy-Reverse`) and existing children (type `System.LinkTypes.Hierarchy-Forward`).
242
+
243
+ #### Notion — Fetch specific page
244
+
245
+ Notion page IDs are UUIDs (32 hex chars, optionally with dashes). The key format in `.clancy/progress.txt` is `notion-{first 8 chars}` for brevity. To fetch, use the full page ID if available, or search the database.
246
+
247
+ ```bash
248
+ RESPONSE=$(curl -s \
249
+ -H "Authorization: Bearer $NOTION_TOKEN" \
250
+ -H "Notion-Version: 2022-06-28" \
251
+ "https://api.notion.com/v1/pages/$PAGE_ID")
252
+ ```
253
+
254
+ If the key is a short `notion-XXXXXXXX` format, query the database instead:
255
+
256
+ ```bash
257
+ RESPONSE=$(curl -s \
258
+ -H "Authorization: Bearer $NOTION_TOKEN" \
259
+ -H "Notion-Version: 2022-06-28" \
260
+ -X POST \
261
+ "https://api.notion.com/v1/databases/$NOTION_DATABASE_ID/query" \
262
+ -d '{"page_size": 100}')
263
+ ```
264
+
265
+ Then match pages by ID prefix.
266
+
267
+ Validate the response:
268
+
269
+ - If `archived` is `true`: warn `Page is archived. Brief it anyway? [y/N]`
270
+
271
+ Fetch comments for existing brief detection:
272
+
273
+ ```bash
274
+ COMMENTS=$(curl -s \
275
+ -H "Authorization: Bearer $NOTION_TOKEN" \
276
+ -H "Notion-Version: 2022-06-28" \
277
+ "https://api.notion.com/v1/comments?block_id=$PAGE_ID")
278
+ ```
279
+
280
+ Map fields: title = extract from `properties` (find the `title` type property), description = fetch page content via `GET /v1/blocks/$PAGE_ID/children` (Notion stores content as blocks, not a single description field), parent = `parent.page_id` or `parent.database_id`.
281
+
282
+ **Notion limitation:** Page content is stored as blocks, not a single text field. Read all child blocks and concatenate their text content for brief context.
283
+
284
+ **Notion limitation:** Comments use `rich_text` format, not markdown. When scanning for `Clancy Strategic Brief`, search for the text content within `rich_text` arrays.
285
+
286
+ #### All platforms — error handling
287
+
288
+ If the API call fails:
289
+
290
+ - 404: `{KEY} not found — check the ticket key.` Stop.
291
+ - 401: `Auth failed — check credentials in .clancy/.env` Stop.
292
+ - 403: `Permission denied — check token scopes.` Stop.
293
+ - 5xx / timeout: `Server error. Try again in a few minutes.` Stop.
294
+ - Network error: `Could not reach {platform} — check network connection.` Stop.
295
+
296
+ ### Inline text mode
297
+
298
+ Use the provided text directly. No API call.
299
+
300
+ ### From file mode
301
+
302
+ Read the file content. Slug derived from filename (strip extension, strip date prefix if present).
303
+
304
+ ### Batch mode
305
+
306
+ Fetch N issues from the planning queue (same labels/statuses as `/clancy:plan`):
307
+
308
+ #### GitHub batch fetch
309
+
310
+ ```bash
311
+ GITHUB_USERNAME=$(curl -s -H "Authorization: Bearer $GITHUB_TOKEN" https://api.github.com/user | jq -r '.login')
312
+
313
+ RESPONSE=$(curl -s \
314
+ -H "Authorization: Bearer $GITHUB_TOKEN" \
315
+ -H "X-GitHub-Api-Version: 2022-11-28" \
316
+ "https://api.github.com/repos/$GITHUB_REPO/issues?state=open&assignee=$GITHUB_USERNAME&labels=$CLANCY_LABEL_PLAN&per_page=$N")
317
+ ```
318
+
319
+ Filter out PRs (entries with `pull_request` key).
320
+
321
+ #### Jira batch fetch
322
+
323
+ ```bash
324
+ RESPONSE=$(curl -s \
325
+ -u "$JIRA_USER:$JIRA_API_TOKEN" \
326
+ -X POST \
327
+ -H "Content-Type: application/json" \
328
+ -H "Accept: application/json" \
329
+ "$JIRA_BASE_URL/rest/api/3/search/jql" \
330
+ -d '{"jql": "project=$JIRA_PROJECT_KEY AND assignee=currentUser() AND status=\"$CLANCY_PLAN_STATUS\" ORDER BY priority ASC", "maxResults": <N>, "fields": ["summary", "description", "status", "issuetype", "parent", "comment"]}')
331
+ ```
332
+
333
+ `CLANCY_PLAN_STATUS` defaults to `Backlog`.
334
+
335
+ #### Linear batch fetch
336
+
337
+ ```graphql
338
+ query {
339
+ viewer {
340
+ assignedIssues(
341
+ filter: {
342
+ state: { type: { eq: "unstarted" } }
343
+ team: { id: { eq: "$LINEAR_TEAM_ID" } }
344
+ }
345
+ first: $N
346
+ orderBy: priority
347
+ ) {
348
+ nodes {
349
+ id
350
+ identifier
351
+ title
352
+ description
353
+ state {
354
+ id
355
+ name
356
+ type
357
+ }
358
+ parent {
359
+ id
360
+ identifier
361
+ title
362
+ }
363
+ children {
364
+ nodes {
365
+ id
366
+ identifier
367
+ title
368
+ state {
369
+ type
370
+ }
371
+ }
372
+ }
373
+ team {
374
+ id
375
+ key
376
+ }
377
+ }
378
+ }
379
+ }
380
+ }
381
+ ```
382
+
383
+ #### Azure DevOps batch fetch
384
+
385
+ ```bash
386
+ # Step 1: WIQL query for planning queue
387
+ WIQL_RESPONSE=$(curl -s \
388
+ -u ":$AZDO_PAT" \
389
+ -X POST \
390
+ -H "Content-Type: application/json" \
391
+ "https://dev.azure.com/$AZDO_ORG/$AZDO_PROJECT/_apis/wit/wiql?api-version=7.1" \
392
+ -d '{"query": "SELECT [System.Id] FROM WorkItems WHERE [System.TeamProject] = '\''$AZDO_PROJECT'\'' AND [System.State] = '\''$CLANCY_PLAN_STATUS'\'' AND [System.Tags] CONTAINS '\''$CLANCY_LABEL_PLAN'\'' AND [System.AssignedTo] = @Me ORDER BY [Microsoft.VSTS.Common.Priority] ASC"}')
393
+
394
+ # Step 2: Batch fetch work items (first N IDs)
395
+ IDS=$(echo "$WIQL_RESPONSE" | jq -r '.workItems[0:'$N'] | map(.id) | join(",")')
396
+
397
+ RESPONSE=$(curl -s \
398
+ -u ":$AZDO_PAT" \
399
+ -H "Accept: application/json" \
400
+ "https://dev.azure.com/$AZDO_ORG/$AZDO_PROJECT/_apis/wit/workitems?ids=$IDS&\$expand=relations&api-version=7.1")
401
+ ```
402
+
403
+ `CLANCY_PLAN_STATUS` defaults to `New`. For each work item, fetch comments separately via `GET /_apis/wit/workitems/{id}/comments?api-version=7.1-preview.4`.
404
+
405
+ #### Shortcut batch fetch
406
+
407
+ Search for stories in the planning workflow state. Shortcut uses workflow states (not labels) for queue filtering, but labels can further narrow results.
408
+
409
+ - `SHORTCUT_WORKFLOW` — optional workflow ID. If not set, use the default workflow.
410
+ - `CLANCY_LABEL_PLAN` — optional label name to filter stories (falls back to `CLANCY_PLAN_LABEL`).
411
+
412
+ ```bash
413
+ RESPONSE=$(curl -s \
414
+ -H "Shortcut-Token: $SHORTCUT_API_TOKEN" \
415
+ -H "Content-Type: application/json" \
416
+ -X POST \
417
+ "https://api.app.shortcut.com/api/v3/stories/search" \
418
+ -d '{"owner_ids": ["<current member UUID>"], "workflow_state_types": ["backlog"], "page_size": <N>}')
419
+ ```
420
+
421
+ To resolve the current member UUID:
422
+
423
+ ```bash
424
+ MEMBER=$(curl -s \
425
+ -H "Shortcut-Token: $SHORTCUT_API_TOKEN" \
426
+ "https://api.app.shortcut.com/api/v3/member-info")
427
+ ```
428
+
429
+ Use `MEMBER.id` as the owner filter. If `CLANCY_LABEL_PLAN` is set, add `"label_ids": [<label_id>]` to the search body (resolve label ID via `GET /api/v3/labels`).
430
+
431
+ For each story, fetch comments separately:
432
+
433
+ ```bash
434
+ COMMENTS=$(curl -s \
435
+ -H "Shortcut-Token: $SHORTCUT_API_TOKEN" \
436
+ "https://api.app.shortcut.com/api/v3/stories/$STORY_ID/comments")
437
+ ```
438
+
439
+ Map fields: title = `name`, description = `description` (markdown), parent = `epic_id` (resolve via `GET /api/v3/epics/$EPIC_ID`), labels = `labels[].name`.
440
+
441
+ #### Notion batch fetch
442
+
443
+ Query the database with status and assignee filters:
444
+
445
+ - `CLANCY_NOTION_STATUS` — the status property name (configurable, defaults auto-detected from database schema)
446
+ - `CLANCY_PLAN_STATUS` — the status value to filter on (defaults to `Backlog` if not set)
447
+ - `CLANCY_NOTION_ASSIGNEE` — the assignee property name (configurable)
448
+ - `CLANCY_LABEL_PLAN` — optional label/tag to filter on (via multi-select property `CLANCY_NOTION_LABELS`)
449
+
450
+ ```bash
451
+ RESPONSE=$(curl -s \
452
+ -H "Authorization: Bearer $NOTION_TOKEN" \
453
+ -H "Notion-Version: 2022-06-28" \
454
+ -X POST \
455
+ "https://api.notion.com/v1/databases/$NOTION_DATABASE_ID/query" \
456
+ -d '{"filter": {"and": [{"property": "$CLANCY_NOTION_STATUS", "status": {"equals": "$CLANCY_PLAN_STATUS"}}, ...]}, "page_size": <N>}')
457
+ ```
458
+
459
+ Add assignee and label filters to the `and` array as needed. Notion filters use property-specific types (`status`, `people`, `multi_select`).
460
+
461
+ For each page, fetch comments separately:
462
+
463
+ ```bash
464
+ COMMENTS=$(curl -s \
465
+ -H "Authorization: Bearer $NOTION_TOKEN" \
466
+ -H "Notion-Version: 2022-06-28" \
467
+ "https://api.notion.com/v1/comments?block_id=$PAGE_ID")
468
+ ```
469
+
470
+ Map fields: title = title property value, description = fetch child blocks via `GET /v1/blocks/$PAGE_ID/children` (concatenate text), parent = `parent.page_id`, labels = multi-select property values.
471
+
472
+ **Notion limitation:** Comments use `rich_text` format, not markdown. When scanning for `Clancy Strategic Brief`, search for the text content within `rich_text` arrays.
473
+
474
+ If no tickets found:
475
+
476
+ ```
477
+ No tickets in the planning queue. Check your queue label/status configuration.
478
+ ```
479
+
480
+ Stop.
481
+
482
+ For batch mode, process each ticket sequentially through Steps 4-10. Skip tickets that already have a brief (check `.clancy/briefs/`). Batch mode always uses AI-grill (no human interaction per ticket).
483
+
484
+ ---
485
+
486
+ ## Step 4 — Grill phase
487
+
488
+ The grill phase is the most critical part of the brief workflow. Its purpose is to walk every branch of the design tree, resolving ambiguity upfront rather than encoding it into vague tickets.
489
+
490
+ ### Mode detection
491
+
492
+ ```
493
+ --afk flag passed? -> AI-GRILL
494
+ CLANCY_MODE=afk in env? -> AI-GRILL
495
+ Batch mode (N tickets)? -> AI-GRILL
496
+ Otherwise -> HUMAN GRILL
497
+ ```
498
+
499
+ The `--afk` flag takes precedence over `CLANCY_MODE`.
500
+
501
+ ### Human grill
502
+
503
+ Interview the user RELENTLESSLY about every aspect of the idea until you reach a shared understanding.
504
+
505
+ **Core principle** (from Matt Pocock's "grill me" skill):
506
+
507
+ > "Interview me relentlessly about every aspect of this plan until we reach a shared understanding. Walk down each branch of the design tree, resolving dependencies between decisions one by one. If a question can be answered by exploring the codebase, explore the codebase instead."
508
+
509
+ **Rules:**
510
+
511
+ 1. Be RELENTLESS. Do not accept vague answers. If the user says "it should be fast", ask "what's the latency budget? 100ms? 500ms? Per-request or p99?" If they say "just pick something", explain the trade-offs and make them choose.
512
+
513
+ 2. For each question, **provide your recommended answer** based on codebase context, board context, or best practices. The user can agree, disagree, or ask for more detail. This speeds up the grill — the user confirms or overrides rather than researching from scratch.
514
+
515
+ 3. Walk each branch of the design tree to its CONCLUSION before moving to the next. Don't jump between topics — follow each thread until it's fully resolved.
516
+
517
+ 4. Explore the codebase instead of asking when the answer is in the code. Don't ask "do you have an auth module?" — check. Then ask informed follow-ups: "I see `src/auth/sso-provider.ts` uses SAML. Should the new feature use the same provider?"
518
+
519
+ 5. This is a TWO-WAY conversation. The user can ask questions back at any time:
520
+ - "What does the codebase currently use?" → explore and answer
521
+ - "What do other projects typically do?" → web research
522
+ - "Are there related tickets?" → board query
523
+ - "What would you recommend?" → give an informed opinion with trade-offs, then let the user decide
524
+
525
+ 6. Answers spawn follow-up questions (multi-round): "We want SSO" → "SAML or OIDC?" → "OIDC" → "Which provider? No OIDC client in codebase yet."
526
+
527
+ 7. Do NOT generate the brief until the grill is complete. The goal is ZERO AMBIGUITY before a single ticket is written. Push back if the user tries to rush: "We still have open questions about X and Y. Let's resolve those first."
528
+
529
+ 8. Stop when you reach a SHARED UNDERSTANDING — both sides agree they understand the full scope, constraints, and decisions. Not just "no more questions" but genuine mutual comprehension.
530
+
531
+ 9. The resolved answers feed into the `## Discovery` section of the brief.
532
+
533
+ **Question categories:**
534
+
535
+ - **Scope:** What's in and what's out?
536
+ - **Users:** Who uses this? What are the personas?
537
+ - **Constraints:** Performance budget? Browser support? Auth?
538
+ - **Edge cases:** What happens when X is empty / fails / times out?
539
+ - **Dependencies:** Does this depend on other in-flight work?
540
+ - **Existing code:** How does this interact with `{module}`?
541
+ - **Data:** What's the data model? Volume? Retention?
542
+ - **Security:** Who can access this? What's the auth boundary?
543
+ - **Observability:** How will you know if this breaks?
544
+
545
+ Typical: 5-20 clarifying questions over 2-5 rounds.
546
+
547
+ ### AI-grill
548
+
549
+ Same relentless energy as the human grill, but directed at the strategist itself via a devil's advocate agent.
550
+
551
+ 1. Generate 10-15 clarifying questions using the same categories as the human grill (scope, users, constraints, edge cases, dependencies, existing code, data, security, observability).
552
+
553
+ 2. Spawn the devil's advocate agent via the Agent tool, passing:
554
+ - The idea text (ticket title + description, or inline text, or file content)
555
+ - The 10-15 generated questions
556
+ - The path to the agent prompt: `src/agents/devils-advocate.md`
557
+
558
+ 3. The devil's advocate agent answers each question by INTERROGATING ITS SOURCES:
559
+ - **Codebase:** explore affected areas, read `.clancy/docs/`, check existing patterns. Don't assume — look.
560
+ - **Board:** parent ticket, related tickets, existing children. Check for conflicting requirements.
561
+ - **Web:** when the question involves external technology, patterns, or third-party integrations. Same trigger as Step 6: `--research` flag forces it, otherwise judgement-based.
562
+
563
+ 4. The agent CHALLENGES ITS OWN ANSWERS. If the codebase says one thing but the ticket description says another, flag the conflict. If a question can be partially answered, answer the part it can and flag the rest. Do NOT accept vague self-answers — if the codebase doesn't clearly support a decision, don't guess.
564
+
565
+ 5. Answers may spawn SELF-FOLLOW-UPS within the same pass: "Should this support SSO?" → checks codebase → finds `src/auth/sso-provider.ts` → "SSO exists, but it's SAML. Should the new feature use SAML or add OIDC?" → checks ticket description → no mention → checks web → "OIDC is the modern standard" → resolves as OIDC with caveat. All resolved in one pass.
566
+
567
+ 6. Single pass — no multi-round loop with the human. But the agent must be thorough enough in one pass that a second would add nothing.
568
+
569
+ 7. The agent NEVER asks the human questions (that defeats `--afk` mode). Unresolvable questions go to `## Open Questions` for the PO to address during brief review.
570
+
571
+ 8. Classify each question:
572
+ - **Answerable** (>80% confidence, or technical decision with clear codebase precedent) → `## Discovery` with source tag
573
+ - **Conflicting evidence** (codebase says X, ticket says Y) → `## Open Questions` with conflict noted
574
+ - **Not answerable** (business decision, ambiguous requirements, no codebase precedent, involves money/legal/compliance/security policy) → `## Open Questions` for PO
575
+
576
+ Typical: 10-15 questions, 8-12 resolved, 2-4 open.
577
+
578
+ ### Output from both modes
579
+
580
+ Both grill modes produce a `## Discovery` section and an `## Open Questions` section. Each Q&A in Discovery includes a source tag:
581
+
582
+ ```
583
+ ## Discovery
584
+
585
+ Q: Should we support system preference detection?
586
+ A: Yes — the codebase already uses `prefers-color-scheme` in
587
+ `src/styles/media.ts`. (Source: codebase)
588
+
589
+ Q: Should dark mode persist across sessions?
590
+ A: Yes, store in localStorage. User confirmed. (Source: human)
591
+
592
+ Q: What's the industry standard for dark mode colour contrast?
593
+ A: WCAG AA requires 4.5:1 ratio for normal text. (Source: web)
594
+
595
+ ## Open Questions
596
+ - [ ] Should dark mode apply to emails/PDFs or just the web UI?
597
+ - [ ] Should portal users see all org data or only their team's?
598
+ (No RBAC policy found in codebase or ticket — needs PO input)
599
+ ```
600
+
601
+ Source tags: `(Source: human)`, `(Source: codebase)`, `(Source: board)`, `(Source: web)`
602
+
603
+ ---
604
+
605
+ ## Step 5 — Auto-detect existing brief
606
+
607
+ Scan `.clancy/briefs/` for an existing brief matching this idea:
608
+
609
+ - **Board ticket:** match by ticket key in the `**Source:**` line
610
+ - **Inline text / file:** match by slug in the filename
611
+
612
+ | Condition | Behaviour |
613
+ | ------------------------------------------- | ----------------------------------------------------------------------------------------------------------------- |
614
+ | No existing brief | Continue to Step 6 (fresh brief) |
615
+ | Existing brief + `--fresh` flag | Delete old file, continue to Step 6 |
616
+ | Existing brief + feedback found | Revise: read existing brief + all feedback, generate revised brief with `### Changes From Previous Brief` section |
617
+ | Existing brief + no feedback + no `--fresh` | Stop: `Already briefed. Add feedback to revise, or use --fresh to start over.` |
618
+
619
+ ### Feedback detection (3 sources, checked in order)
620
+
621
+ 1. **Local brief file** — check for `## Feedback` section appended to `.clancy/briefs/{date}-{slug}.md`
622
+ 2. **Companion file** — check for `.clancy/briefs/{date}-{slug}.feedback.md`
623
+ 3. **Board comments** (board-sourced only) — fetch ALL comments on the source ticket. Scan each comment body for the text `Clancy Strategic Brief` (case-insensitive, match anywhere in the body — it may appear as `# Clancy Strategic Brief`, `## Clancy Strategic Brief`, or just the text). The most recent matching comment is the brief. Collect all comments posted AFTER it as feedback.
624
+
625
+ Board comment feedback filtering (all platforms): collect all comments posted AFTER the brief comment. Exclude any comment that itself contains `Clancy Strategic Brief` (case-insensitive) — these are Clancy-generated brief postings, not human feedback. All other post-brief comments are treated as feedback regardless of author, since Clancy posts using the user's own credentials.
626
+
627
+ Merge order: local `## Feedback` section first, then `.feedback.md` file, then board comments (chronological). All passed to generation step as additional context.
628
+
629
+ ### Edge cases
630
+
631
+ - **Board comment exists but local file is missing:** Re-download the brief from the board comment into `.clancy/briefs/`. Then check for feedback normally.
632
+ - **Local file exists but board comment was deleted:** Use local feedback only. No board feedback to read.
633
+ - **Multiple brief comments on same ticket:** Use the MOST RECENT (latest timestamp) as the reference point for feedback detection.
634
+
635
+ ---
636
+
637
+ ## Step 6 — Relevance check
638
+
639
+ Read `.clancy/docs/STACK.md` and `ARCHITECTURE.md` (if they exist). Compare the idea's domain against the codebase technology stack.
640
+
641
+ If the idea is clearly irrelevant (targets a platform or technology completely outside the codebase):
642
+
643
+ ```
644
+ Skipping — this idea targets {platform}, but this codebase is {stack}.
645
+ ```
646
+
647
+ Log: `YYYY-MM-DD HH:MM | BRIEF | {slug} | SKIPPED — not relevant ({reason})`
648
+ Stop.
649
+
650
+ If the idea mentions a technology not listed in STACK.md, flag it as a concern but do NOT skip — include a note in the brief's Technical Considerations section.
651
+
652
+ ---
653
+
654
+ ## Step 7 — Research (adaptive agents)
655
+
656
+ Assess complexity from the idea title + description:
657
+
658
+ | Complexity | Agents | Trigger |
659
+ | ------------------------------------------ | ----------------- | ------------ |
660
+ | Narrow (single feature, few files) | 1 codebase agent | Simple scope |
661
+ | Moderate (multi-component, clear boundary) | 2 codebase agents | Medium scope |
662
+ | Broad (cross-cutting, multiple subsystems) | 3 codebase agents | Large scope |
663
+
664
+ Web research agent (adds 1 to the count above, max 4 total):
665
+
666
+ - `--research` flag → always add web agent
667
+ - Idea involves new/external technology → add web agent (judgement-based)
668
+ - Internal refactor → no web agent
669
+
670
+ ### What agents explore
671
+
672
+ - `.clancy/docs/` — STACK.md, ARCHITECTURE.md, CONVENTIONS.md, TESTING.md, DESIGN-SYSTEM.md
673
+ - Affected code areas via Glob + Read
674
+ - Board for duplicates/related tickets (text-match against title):
675
+ - **GitHub:** `GET /repos/$GITHUB_REPO/issues?state=open&per_page=30` and text-match
676
+ - **Jira:** `POST /rest/api/3/search/jql` with `summary ~ "keywords"`
677
+ - **Linear:** `issues(filter: ...)` by text search
678
+ - **Azure DevOps:** `POST /_apis/wit/wiql` with `[System.Title] CONTAINS 'keywords'`
679
+ - **Shortcut:** `POST /api/v3/stories/search` with `{"query": "keywords"}`
680
+ - **Notion:** `POST /v1/databases/$NOTION_DATABASE_ID/query` with title text filter
681
+ - Existing children of the source ticket (board-sourced only):
682
+ - **GitHub:** scan open issues for `Epic: #{parent}` in body
683
+ - **Jira:** `POST /rest/api/3/search/jql` with `parent = {KEY}`
684
+ - **Linear:** already included in the fetch response (`children.nodes`)
685
+ - **Azure DevOps:** check `relations` array for `System.LinkTypes.Hierarchy-Forward` entries
686
+ - **Shortcut:** check `story_links` array for related stories; if `epic_id` is set, fetch epic stories via `GET /api/v3/epics/$EPIC_ID/stories`
687
+ - **Notion:** check `relation` properties on the page for linked child pages
688
+ - Web research (if triggered)
689
+
690
+ Display per-agent progress:
691
+
692
+ ```
693
+ Researching...
694
+ Agent 1: Codebase structure ✅
695
+ Agent 2: Testing patterns ✅
696
+ Agent 3: Web research ✅ (3 sources)
697
+ ```
698
+
699
+ ---
700
+
701
+ ## Step 8 — Generate brief
702
+
703
+ Using all gathered context (idea, grill output, research findings), generate the brief in this exact template:
704
+
705
+ ```markdown
706
+ # Clancy Strategic Brief
707
+
708
+ **Source:** {source — see below}
709
+ **Date:** {YYYY-MM-DD}
710
+ **Status:** Draft
711
+
712
+ ---
713
+
714
+ ## Problem Statement
715
+
716
+ {2-4 sentences: what problem does this solve and why does it matter?}
717
+
718
+ ## Goals
719
+
720
+ - {Specific, measurable goal}
721
+ - {Specific, measurable goal}
722
+
723
+ ## Non-Goals
724
+
725
+ - {What is explicitly out of scope}
726
+ - {What is explicitly out of scope}
727
+
728
+ ## Discovery
729
+
730
+ {Q&A pairs from the grill phase, each with source tag — see Step 4 output format}
731
+
732
+ ## Background Research
733
+
734
+ {Findings from codebase exploration and web research. Include file paths, patterns found, and external references.}
735
+
736
+ ## Related Existing Work
737
+
738
+ {Existing tickets, PRs, or code that overlaps with this idea. If the source ticket has children, list them here. "None found" if clean.}
739
+
740
+ ## User Stories
741
+
742
+ - As a {persona}, I want to {action} so that {outcome}.
743
+ - As a {persona}, I want to {action} so that {outcome}.
744
+ - As a {persona}, I want to {action} so that {outcome}.
745
+
746
+ ## Technical Considerations
747
+
748
+ - {Architectural decisions, patterns to follow, constraints}
749
+ - {Integration points, migration needs, backwards compatibility}
750
+ - {Performance, security, accessibility considerations}
751
+
752
+ ## Ticket Decomposition
753
+
754
+ | # | Title | Description | Size | Deps | Mode |
755
+ | --- | ---------------------- | --------------- | ---- | ---- | ---- |
756
+ | 1 | {Vertical slice title} | {1-2 sentences} | S | — | AFK |
757
+ | 2 | {Vertical slice title} | {1-2 sentences} | M | #1 | AFK |
758
+ | 3 | {Vertical slice title} | {1-2 sentences} | M | #1 | HITL |
759
+
760
+ ## Open Questions
761
+
762
+ - [ ] {Unresolved question from grill phase — with reason}
763
+ - [ ] {Unresolved question — needs PO input}
764
+
765
+ ## Success Criteria
766
+
767
+ - [ ] {Specific, testable criterion for the entire initiative}
768
+ - [ ] {Specific, testable criterion}
769
+
770
+ ## Risks
771
+
772
+ - {Specific risk and mitigation strategy}
773
+ - {Specific risk and mitigation strategy}
774
+
775
+ ---
776
+
777
+ _Generated by [Clancy](https://github.com/Pushedskydiver/chief-clancy). To answer open questions or request changes: comment on the source ticket or add a ## Feedback section to the brief file, then re-run `/clancy:brief` to revise. To approve: `/clancy:approve-brief`. To start over: `/clancy:brief --fresh`._
778
+ ```
779
+
780
+ ### Source field format
781
+
782
+ - **Board ticket:** `[{KEY}] {Title}` (e.g. `[#50] Redesign settings page`, `[PROJ-200] Add customer portal`, `[ENG-42] Add real-time notifications`)
783
+ - **Inline text:** `"{text}"` (e.g. `"Add dark mode support"`)
784
+ - **From file:** `{path}` (e.g. `docs/rfcs/auth-rework.md`)
785
+
786
+ ### Ticket Decomposition rules
787
+
788
+ 1. **Max 10 tickets.** If the idea needs more, note "Consider splitting this initiative into multiple briefs."
789
+ 2. **Vertical slices only.** Each ticket must cut through all layers needed to deliver one thin, working piece of functionality end-to-end. If a ticket title mentions only one layer (e.g. "Set up database schema", "Create React components"), restructure it into a slice that delivers observable behaviour.
790
+ 3. **Dependencies** reference other tickets in the table by `#N` (e.g. `#1`, `#1, #3`). Use `—` for no dependencies.
791
+ 4. **Size:** S (< 1 hour, few files), M (1-4 hours, moderate), L (4+ hours, significant).
792
+ 5. **Mode:**
793
+ - `AFK` — ticket can be implemented autonomously by `/clancy:implement` or `/clancy:autopilot`
794
+ - `HITL` — ticket requires human judgement, approval, or input (design decisions, credentials, external service setup, UX review, ambiguous requirements)
795
+ 6. Every ticket must trace to at least one user story.
796
+
797
+ ### User Story rules
798
+
799
+ - Write 3-8 user stories per brief (more = scope too large)
800
+ - Each story must be testable (implies acceptance criteria)
801
+ - Use the format: `As a {persona}, I want to {action} so that {outcome}.`
802
+
803
+ ### Re-brief revision
804
+
805
+ If revising from feedback (Step 5):
806
+
807
+ 1. **Cross-reference feedback against Open Questions.** For each Open Question in the existing brief, check if the feedback contains an answer (exact or paraphrased — match by intent, not syntax). Resolved questions move to `## Discovery` with `(Source: human)` tag. Unresolved questions stay in `## Open Questions`.
808
+
809
+ 2. **Apply all other feedback** — changes to scope, goals, decomposition, user stories, etc.
810
+
811
+ 3. **Prepend a section** before Problem Statement:
812
+
813
+ ```markdown
814
+ ### Changes From Previous Brief
815
+
816
+ {What feedback was addressed and how the brief changed.
817
+ List resolved open questions explicitly.}
818
+ ```
819
+
820
+ ---
821
+
822
+ ## Step 9 — Save locally
823
+
824
+ Write to `.clancy/briefs/{YYYY-MM-DD}-{slug}.md`.
825
+
826
+ **Slug generation:**
827
+
828
+ - **Board ticket:** derive from title — lowercase, replace non-alphanumeric with hyphens, trim, truncate to 50 chars. E.g. `add-customer-portal`.
829
+ - **Inline text:** derive from the text — same rules.
830
+ - **From file:** derive from filename — strip extension, strip date prefix if present.
831
+
832
+ **Slug collision:** If file already exists, append `-2`, `-3`, etc.
833
+
834
+ **Create `.clancy/briefs/` directory** if it does not exist.
835
+
836
+ ---
837
+
838
+ ## Step 10 — Post to board
839
+
840
+ Only for board-sourced briefs (ticket key was provided). Inline text and file briefs are local only.
841
+
842
+ ### GitHub — POST comment
843
+
844
+ ```bash
845
+ curl -s \
846
+ -H "Authorization: Bearer $GITHUB_TOKEN" \
847
+ -H "X-GitHub-Api-Version: 2022-11-28" \
848
+ -X POST \
849
+ "https://api.github.com/repos/$GITHUB_REPO/issues/$ISSUE_NUMBER/comments" \
850
+ -d '{"body": "<full brief markdown>"}'
851
+ ```
852
+
853
+ GitHub accepts Markdown directly.
854
+
855
+ ### Jira — POST comment
856
+
857
+ ```bash
858
+ curl -s \
859
+ -u "$JIRA_USER:$JIRA_API_TOKEN" \
860
+ -X POST \
861
+ -H "Content-Type: application/json" \
862
+ -H "Accept: application/json" \
863
+ "$JIRA_BASE_URL/rest/api/3/issue/$TICKET_KEY/comment" \
864
+ -d '<ADF JSON body>'
865
+ ```
866
+
867
+ Construct ADF (Atlassian Document Format) JSON for the comment body:
868
+
869
+ - `## Heading` → `heading` node (level 2)
870
+ - `### Heading` → `heading` node (level 3)
871
+ - `- bullet` → `bulletList > listItem > paragraph`
872
+ - `| table |` → `table > tableRow > tableCell`
873
+ - `**bold**` → marks: `[{ "type": "strong" }]`
874
+ - `` `code` `` → marks: `[{ "type": "code" }]`
875
+
876
+ If ADF construction is too complex for a particular element, fall back to wrapping that section in a `codeBlock` node.
877
+
878
+ Comment marker heading: `## Clancy Strategic Brief` (H2 ADF heading node).
879
+
880
+ ### Linear — commentCreate mutation
881
+
882
+ ```bash
883
+ curl -s \
884
+ -X POST \
885
+ -H "Content-Type: application/json" \
886
+ -H "Authorization: $LINEAR_API_KEY" \
887
+ "https://api.linear.app/graphql" \
888
+ -d '{"query": "mutation { commentCreate(input: { issueId: \"$ISSUE_ID\", body: \"<full brief markdown>\" }) { success } }"}'
889
+ ```
890
+
891
+ Linear accepts Markdown directly. Comment marker heading: `# Clancy Strategic Brief`.
892
+
893
+ Note: Linear personal API keys do NOT use `Bearer` prefix.
894
+
895
+ ### Azure DevOps — POST comment
896
+
897
+ ```bash
898
+ curl -s \
899
+ -u ":$AZDO_PAT" \
900
+ -X POST \
901
+ -H "Content-Type: application/json" \
902
+ "https://dev.azure.com/$AZDO_ORG/$AZDO_PROJECT/_apis/wit/workitems/$WORK_ITEM_ID/comments?api-version=7.1-preview.4" \
903
+ -d '{"text": "<brief as HTML>"}'
904
+ ```
905
+
906
+ Azure DevOps comments use **HTML**, not markdown. Convert the brief markdown to HTML (same rules as planner: headings → `<h2>/<h3>`, bullets → `<ul><li>`, bold → `<strong>`, code → `<code>`, tables → `<table>`). If HTML construction is too complex, wrap in `<pre>` tags as fallback.
907
+
908
+ Comment marker heading: `<h2>Clancy Strategic Brief</h2>`.
909
+
910
+ ### Shortcut — POST comment
911
+
912
+ ```bash
913
+ curl -s \
914
+ -H "Shortcut-Token: $SHORTCUT_API_TOKEN" \
915
+ -H "Content-Type: application/json" \
916
+ -X POST \
917
+ "https://api.app.shortcut.com/api/v3/stories/$STORY_ID/comments" \
918
+ -d '{"text": "<full brief markdown>"}'
919
+ ```
920
+
921
+ Shortcut accepts Markdown directly in comment text.
922
+
923
+ Comment marker heading: `# Clancy Strategic Brief`.
924
+
925
+ ### Notion — POST comment
926
+
927
+ ```bash
928
+ curl -s \
929
+ -H "Authorization: Bearer $NOTION_TOKEN" \
930
+ -H "Notion-Version: 2022-06-28" \
931
+ -X POST \
932
+ "https://api.notion.com/v1/comments" \
933
+ -d '{"parent": {"page_id": "$PAGE_ID"}, "rich_text": [{"type": "text", "text": {"content": "<brief text>"}}]}'
934
+ ```
935
+
936
+ **Notion limitation:** Comments use `rich_text` blocks, not markdown. For the brief content, use a single `text` block with the full brief as plain text. Notion will render it without markdown formatting. For better readability, consider splitting the brief into multiple `rich_text` blocks (one per section) with `annotations` for bold headings.
937
+
938
+ **Notion limitation:** The `rich_text` array has a **2000-character limit per text block**. If the brief exceeds 2000 characters, split it across multiple `rich_text` blocks within the same comment (each block up to 2000 chars). The total comment can contain many blocks.
939
+
940
+ Comment marker: include `Clancy Strategic Brief` as the first text content in the `rich_text` array.
941
+
942
+ ### On failure (any platform)
943
+
944
+ ```
945
+ ⚠️ Failed to post brief comment on {KEY}. Brief saved locally at .clancy/briefs/{file}. Paste it manually.
946
+ ```
947
+
948
+ Continue — do not stop. The local file is the source of truth.
949
+
950
+ ---
951
+
952
+ ## Step 10a — Apply pipeline label (board-sourced only)
953
+
954
+ Only for board-sourced briefs (ticket key was provided). Inline text and file briefs skip this step.
955
+
956
+ **This step is mandatory for board-sourced briefs — always apply the label.** Use `CLANCY_LABEL_BRIEF` from `.clancy/.env` if set. If not set, use `clancy:brief` as the default. Ensure the label exists on the board (create it if missing), then add it to the ticket. Also read `CLANCY_LABEL_PLAN` (default: `clancy:plan`) and `CLANCY_LABEL_BUILD` (default: `clancy:build`) for cleanup during re-briefs.
957
+
958
+ ### Re-brief cleanup (`--fresh` flag)
959
+
960
+ If this is a re-brief (`--fresh`), the ticket may already have `clancy:plan` or `clancy:build` from a prior approval. Remove them first (best-effort — ignore failures):
961
+
962
+ #### GitHub
963
+
964
+ ```bash
965
+ # Remove plan label (ignore 404)
966
+ curl -s \
967
+ -H "Authorization: Bearer $GITHUB_TOKEN" \
968
+ -H "Accept: application/vnd.github+json" \
969
+ -H "X-GitHub-Api-Version: 2022-11-28" \
970
+ -X DELETE \
971
+ "https://api.github.com/repos/$GITHUB_REPO/issues/$ISSUE_NUMBER/labels/$(echo $CLANCY_LABEL_PLAN | jq -Rr @uri)"
972
+
973
+ # Remove build label (ignore 404)
974
+ curl -s \
975
+ -H "Authorization: Bearer $GITHUB_TOKEN" \
976
+ -H "Accept: application/vnd.github+json" \
977
+ -H "X-GitHub-Api-Version: 2022-11-28" \
978
+ -X DELETE \
979
+ "https://api.github.com/repos/$GITHUB_REPO/issues/$ISSUE_NUMBER/labels/$(echo $CLANCY_LABEL_BUILD | jq -Rr @uri)"
980
+ ```
981
+
982
+ #### Jira
983
+
984
+ ```bash
985
+ # Fetch current labels, remove plan + build labels, PUT updated list
986
+ CURRENT_LABELS=$(curl -s \
987
+ -u "$JIRA_USER:$JIRA_API_TOKEN" \
988
+ -H "Accept: application/json" \
989
+ "$JIRA_BASE_URL/rest/api/3/issue/$TICKET_KEY?fields=labels" | jq -r '.fields.labels')
990
+
991
+ UPDATED_LABELS=$(echo "$CURRENT_LABELS" | jq --arg plan "$CLANCY_LABEL_PLAN" --arg build "$CLANCY_LABEL_BUILD" '[.[] | select(. != $plan and . != $build)]')
992
+
993
+ curl -s \
994
+ -u "$JIRA_USER:$JIRA_API_TOKEN" \
995
+ -X PUT \
996
+ -H "Content-Type: application/json" \
997
+ "$JIRA_BASE_URL/rest/api/3/issue/$TICKET_KEY" \
998
+ -d "{\"fields\": {\"labels\": $UPDATED_LABELS}}"
999
+ ```
1000
+
1001
+ #### Linear
1002
+
1003
+ ```bash
1004
+ # Fetch current label IDs, remove plan + build label IDs, update issue
1005
+ # Query current labels on the issue
1006
+ ISSUE_DATA=$(curl -s \
1007
+ -X POST \
1008
+ -H "Content-Type: application/json" \
1009
+ -H "Authorization: $LINEAR_API_KEY" \
1010
+ "https://api.linear.app/graphql" \
1011
+ -d '{"query": "query { issues(filter: { identifier: { eq: \"$IDENTIFIER\" } }) { nodes { id labels { nodes { id name } } } } }"}')
1012
+
1013
+ # Filter out plan + build label IDs, then issueUpdate with remaining labelIds
1014
+ ```
1015
+
1016
+ #### Azure DevOps
1017
+
1018
+ ```bash
1019
+ # Fetch current tags, remove plan + build tags from semicolon-delimited string
1020
+ CURRENT=$(curl -s \
1021
+ -u ":$AZDO_PAT" \
1022
+ -H "Accept: application/json" \
1023
+ "https://dev.azure.com/$AZDO_ORG/$AZDO_PROJECT/_apis/wit/workitems/$WORK_ITEM_ID?fields=System.Tags&api-version=7.1")
1024
+
1025
+ # Parse tags, remove plan + build tags, rejoin with "; " separator
1026
+ # E.g., "clancy:plan; clancy:build; feature" → "feature"
1027
+ curl -s \
1028
+ -u ":$AZDO_PAT" \
1029
+ -X PATCH \
1030
+ -H "Content-Type: application/json-patch+json" \
1031
+ "https://dev.azure.com/$AZDO_ORG/$AZDO_PROJECT/_apis/wit/workitems/$WORK_ITEM_ID?api-version=7.1" \
1032
+ -d '[{"op": "replace", "path": "/fields/System.Tags", "value": "<tags without plan/build>"}]'
1033
+ ```
1034
+
1035
+ #### Shortcut
1036
+
1037
+ ```bash
1038
+ # Fetch current story labels, filter out plan + build labels, update
1039
+ STORY=$(curl -s \
1040
+ -H "Shortcut-Token: $SHORTCUT_API_TOKEN" \
1041
+ "https://api.app.shortcut.com/api/v3/stories/$STORY_ID")
1042
+
1043
+ # Remove plan + build labels from the labels array, then update story:
1044
+ curl -s \
1045
+ -H "Shortcut-Token: $SHORTCUT_API_TOKEN" \
1046
+ -H "Content-Type: application/json" \
1047
+ -X PUT \
1048
+ "https://api.app.shortcut.com/api/v3/stories/$STORY_ID" \
1049
+ -d '{"labels": [labels_without_plan_and_build]}'
1050
+ ```
1051
+
1052
+ #### Notion
1053
+
1054
+ ```bash
1055
+ # Fetch current page properties, remove plan + build labels from multi-select
1056
+ PAGE=$(curl -s \
1057
+ -H "Authorization: Bearer $NOTION_TOKEN" \
1058
+ -H "Notion-Version: 2022-06-28" \
1059
+ "https://api.notion.com/v1/pages/$PAGE_ID")
1060
+
1061
+ # Update multi-select property without plan + build labels
1062
+ curl -s \
1063
+ -H "Authorization: Bearer $NOTION_TOKEN" \
1064
+ -H "Notion-Version: 2022-06-28" \
1065
+ -X PATCH \
1066
+ "https://api.notion.com/v1/pages/$PAGE_ID" \
1067
+ -d '{"properties": {"$CLANCY_NOTION_LABELS": {"multi_select": [options_without_plan_and_build]}}}'
1068
+ ```
1069
+
1070
+ ### Add brief label
1071
+
1072
+ Ensure the label exists and add it to the ticket. Best-effort — warn on failure, never stop.
1073
+
1074
+ #### GitHub
1075
+
1076
+ ```bash
1077
+ # Ensure label exists (ignore 422 = already exists)
1078
+ curl -s \
1079
+ -H "Authorization: Bearer $GITHUB_TOKEN" \
1080
+ -H "Accept: application/vnd.github+json" \
1081
+ -H "X-GitHub-Api-Version: 2022-11-28" \
1082
+ -H "Content-Type: application/json" \
1083
+ -X POST \
1084
+ "https://api.github.com/repos/$GITHUB_REPO/labels" \
1085
+ -d '{"name": "$CLANCY_LABEL_BRIEF", "color": "0075ca"}'
1086
+
1087
+ # Add label to issue
1088
+ curl -s \
1089
+ -H "Authorization: Bearer $GITHUB_TOKEN" \
1090
+ -H "Accept: application/vnd.github+json" \
1091
+ -H "X-GitHub-Api-Version: 2022-11-28" \
1092
+ -H "Content-Type: application/json" \
1093
+ -X POST \
1094
+ "https://api.github.com/repos/$GITHUB_REPO/issues/$ISSUE_NUMBER/labels" \
1095
+ -d '{"labels": ["$CLANCY_LABEL_BRIEF"]}'
1096
+ ```
1097
+
1098
+ #### Jira
1099
+
1100
+ ```bash
1101
+ # Jira auto-creates labels — just add to the issue's label array
1102
+ CURRENT_LABELS=$(curl -s \
1103
+ -u "$JIRA_USER:$JIRA_API_TOKEN" \
1104
+ -H "Accept: application/json" \
1105
+ "$JIRA_BASE_URL/rest/api/3/issue/$TICKET_KEY?fields=labels" | jq -r '.fields.labels')
1106
+
1107
+ UPDATED_LABELS=$(echo "$CURRENT_LABELS" | jq --arg brief "$CLANCY_LABEL_BRIEF" '. + [$brief] | unique')
1108
+
1109
+ curl -s \
1110
+ -u "$JIRA_USER:$JIRA_API_TOKEN" \
1111
+ -X PUT \
1112
+ -H "Content-Type: application/json" \
1113
+ "$JIRA_BASE_URL/rest/api/3/issue/$TICKET_KEY" \
1114
+ -d "{\"fields\": {\"labels\": $UPDATED_LABELS}}"
1115
+ ```
1116
+
1117
+ #### Linear
1118
+
1119
+ ```bash
1120
+ # Ensure label exists (check team labels, workspace labels, create if missing)
1121
+ # Then add to issue via issueUpdate with updated labelIds array
1122
+ curl -s \
1123
+ -X POST \
1124
+ -H "Content-Type: application/json" \
1125
+ -H "Authorization: $LINEAR_API_KEY" \
1126
+ "https://api.linear.app/graphql" \
1127
+ -d '{"query": "mutation { issueLabelCreate(input: { teamId: \"$LINEAR_TEAM_ID\", name: \"$CLANCY_LABEL_BRIEF\", color: \"#0075ca\" }) { success issueLabel { id } } }"}'
1128
+
1129
+ # Add label to issue (fetch current labelIds, append new, issueUpdate)
1130
+ ```
1131
+
1132
+ #### Azure DevOps
1133
+
1134
+ ```bash
1135
+ # Fetch current tags, append brief tag
1136
+ CURRENT=$(curl -s \
1137
+ -u ":$AZDO_PAT" \
1138
+ -H "Accept: application/json" \
1139
+ "https://dev.azure.com/$AZDO_ORG/$AZDO_PROJECT/_apis/wit/workitems/$WORK_ITEM_ID?fields=System.Tags&api-version=7.1")
1140
+
1141
+ # Append brief tag to semicolon-delimited string
1142
+ curl -s \
1143
+ -u ":$AZDO_PAT" \
1144
+ -X PATCH \
1145
+ -H "Content-Type: application/json-patch+json" \
1146
+ "https://dev.azure.com/$AZDO_ORG/$AZDO_PROJECT/_apis/wit/workitems/$WORK_ITEM_ID?api-version=7.1" \
1147
+ -d '[{"op": "replace", "path": "/fields/System.Tags", "value": "<existing tags>; $CLANCY_LABEL_BRIEF"}]'
1148
+ ```
1149
+
1150
+ #### Shortcut
1151
+
1152
+ ```bash
1153
+ # Resolve brief label ID (create if missing)
1154
+ LABELS=$(curl -s \
1155
+ -H "Shortcut-Token: $SHORTCUT_API_TOKEN" \
1156
+ "https://api.app.shortcut.com/api/v3/labels")
1157
+
1158
+ # Find or create the brief label, get its ID
1159
+ # Then add to story (merge with existing labels):
1160
+ curl -s \
1161
+ -H "Shortcut-Token: $SHORTCUT_API_TOKEN" \
1162
+ -H "Content-Type: application/json" \
1163
+ -X PUT \
1164
+ "https://api.app.shortcut.com/api/v3/stories/$STORY_ID" \
1165
+ -d '{"labels": [{"name": "$CLANCY_LABEL_BRIEF"}, ...existing_labels]}'
1166
+ ```
1167
+
1168
+ #### Notion
1169
+
1170
+ ```bash
1171
+ # Fetch current page properties, add brief label to multi-select
1172
+ PAGE=$(curl -s \
1173
+ -H "Authorization: Bearer $NOTION_TOKEN" \
1174
+ -H "Notion-Version: 2022-06-28" \
1175
+ "https://api.notion.com/v1/pages/$PAGE_ID")
1176
+
1177
+ # Update multi-select property to include brief label
1178
+ curl -s \
1179
+ -H "Authorization: Bearer $NOTION_TOKEN" \
1180
+ -H "Notion-Version: 2022-06-28" \
1181
+ -X PATCH \
1182
+ "https://api.notion.com/v1/pages/$PAGE_ID" \
1183
+ -d '{"properties": {"$CLANCY_NOTION_LABELS": {"multi_select": [existing_options, {"name": "$CLANCY_LABEL_BRIEF"}]}}}'
1184
+ ```
1185
+
1186
+ #### On failure (any platform)
1187
+
1188
+ ```
1189
+ ⚠️ Could not add pipeline label to {KEY}. The brief was saved and posted successfully — label it manually if needed.
1190
+ ```
1191
+
1192
+ Continue — do not stop.
1193
+
1194
+ ---
1195
+
1196
+ ## Step 11 — Brief inventory (`--list`)
1197
+
1198
+ If `--list` flag is present, display an inventory of all briefs and stop.
1199
+
1200
+ Scan `.clancy/briefs/` for all `.md` files. For each file:
1201
+
1202
+ - Parse date from filename prefix (`YYYY-MM-DD-slug.md`)
1203
+ - Parse `**Source:**` line
1204
+ - Parse Status (check for `.approved` marker file)
1205
+ - Parse ticket count from decomposition table (`?` if unparseable)
1206
+ - Calculate age (today - date)
1207
+ - Check stale: unapproved + age > 7 days
1208
+
1209
+ Sort by date (newest first). Display:
1210
+
1211
+ ```
1212
+ Clancy — Briefs
1213
+ ================================================================
1214
+
1215
+ [1] dark-mode-support 2026-03-14 Draft 3 tickets Source: #50
1216
+ [2] customer-portal 2026-03-13 Approved 8 tickets Source: PROJ-200 ✅
1217
+ [3] real-time-notifications 2026-03-12 Draft 4 tickets Source: ENG-42
1218
+ [4] auth-rework 2026-03-05 Draft 6 tickets Source: file STALE (9 days)
1219
+
1220
+ 3 unapproved drafts. 1 stale (>7 days).
1221
+
1222
+ To approve: /clancy:approve-brief <slug or index>
1223
+ To review stale briefs: open the file and add ## Feedback, or delete it.
1224
+ ```
1225
+
1226
+ If `.clancy/briefs/` does not exist or is empty:
1227
+
1228
+ ```
1229
+ No briefs found. Run /clancy:brief to create one.
1230
+ ```
1231
+
1232
+ Stop.
1233
+
1234
+ ---
1235
+
1236
+ ## Step 12 — Display
1237
+
1238
+ Print the full brief to stdout, followed by the sign-off:
1239
+
1240
+ ```
1241
+ "I'm going to need to ask you some questions, and I want them answered immediately."
1242
+ ```
1243
+
1244
+ ### Next steps (board-sourced)
1245
+
1246
+ ```
1247
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
1248
+ Next Steps
1249
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
1250
+
1251
+ Answer open questions or request changes:
1252
+ • Comment on {KEY} on your board
1253
+ • Or add a ## Feedback section to the brief file
1254
+ Then re-run: /clancy:brief {KEY}
1255
+
1256
+ Approve: /clancy:approve-brief {KEY}
1257
+ Start over: /clancy:brief --fresh {KEY}
1258
+ ```
1259
+
1260
+ ### Next steps (inline text / from file)
1261
+
1262
+ ```
1263
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
1264
+ Next Steps
1265
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
1266
+
1267
+ Answer open questions or request changes:
1268
+ • Add a ## Feedback section to:
1269
+ .clancy/briefs/{date}-{slug}.md
1270
+ • Or create a companion file:
1271
+ .clancy/briefs/{date}-{slug}.feedback.md
1272
+ Then re-run: /clancy:brief
1273
+
1274
+ Approve: /clancy:approve-brief {slug}
1275
+ With parent: /clancy:approve-brief {slug} --epic {KEY}
1276
+ Start over: /clancy:brief --fresh
1277
+ ```
1278
+
1279
+ ---
1280
+
1281
+ ## Step 13 — Log
1282
+
1283
+ Append to `.clancy/progress.txt`:
1284
+
1285
+ | Outcome | Log entry |
1286
+ | ----------------------------- | -------------------------------------------------------------------------- |
1287
+ | Brief generated | `YYYY-MM-DD HH:MM \| BRIEF \| {slug} \| {N} proposed tickets` |
1288
+ | Brief revised (from feedback) | `YYYY-MM-DD HH:MM \| BRIEF \| {slug} \| REVISED - {N} proposed tickets` |
1289
+ | Brief skipped (not relevant) | `YYYY-MM-DD HH:MM \| BRIEF \| {slug} \| SKIPPED - not relevant ({reason})` |
1290
+ | Brief skipped (ticket Done) | `YYYY-MM-DD HH:MM \| BRIEF \| {slug} \| SKIPPED - ticket is Done` |
1291
+ | Brief skipped (not found) | `YYYY-MM-DD HH:MM \| BRIEF \| {slug} \| SKIPPED - ticket not found` |
1292
+ | Already briefed (no feedback) | (nothing logged) |
1293
+ | `--list` display | (nothing logged) |
1294
+ | Auth/network failure | (nothing logged) |
1295
+
1296
+ ---
1297
+
1298
+ ## Step 14 — Batch summary
1299
+
1300
+ After all tickets in a batch are processed, display:
1301
+
1302
+ ```
1303
+ Briefed {M} of {N} tickets. {K} skipped.
1304
+
1305
+ ✅ [{KEY1}] {Title} — 4 tickets proposed
1306
+ ✅ [{KEY2}] {Title} — 6 tickets proposed
1307
+ ⏭️ [{KEY3}] {Title} — already briefed
1308
+ ⏭️ [{KEY4}] {Title} — not relevant
1309
+
1310
+ Briefs saved to .clancy/briefs/. Run /clancy:approve-brief to create tickets.
1311
+ ```
1312
+
1313
+ ---
1314
+
1315
+ ## Notes
1316
+
1317
+ - This command does NOT create tickets — it generates briefs only. Ticket creation is `/clancy:approve-brief`.
1318
+ - Briefs are saved locally in `.clancy/briefs/` and optionally posted as comments on the source ticket.
1319
+ - The grill phase is the most important part — do not skip or rush it. Zero ambiguity is the goal.
1320
+ - Re-running without `--fresh` auto-detects feedback: if feedback exists, revises; if no feedback, stops with guidance.
1321
+ - The `--fresh` flag discards the existing brief entirely and generates a new one from scratch.
1322
+ - The `--list` flag is an inventory display only — no brief generated, no API calls beyond the local filesystem.
1323
+ - Batch mode (`/clancy:brief 3`) implies AI-grill — each ticket is briefed autonomously.
1324
+ - All board API calls are best-effort — if a comment fails to post, print the brief and warn. The local file is the source of truth.
1325
+ - The `Clancy Strategic Brief` text in comments is the marker used by both `/clancy:brief` (to detect existing briefs and feedback) and `/clancy:approve-brief` (to find the brief). Search case-insensitively and match regardless of heading level (`#`, `##`, or plain text).
1326
+ - Jira uses ADF for comments (with `codeBlock` fallback). GitHub, Linear, and Shortcut accept Markdown directly.
1327
+ - Linear personal API keys do NOT use `Bearer` prefix.
1328
+ - Shortcut uses `Shortcut-Token` header (not `Authorization: Bearer`). API base: `https://api.app.shortcut.com/api/v3`. Stories use `name` (not `title`), `description` is markdown, dependencies use `story_links` with `blocks` verb, and workflow state transitions use `workflow_state_id`.
1329
+ - Notion uses `Authorization: Bearer $NOTION_TOKEN` and `Notion-Version: 2022-06-28` headers. API base: `https://api.notion.com/v1`. Comments use `rich_text` blocks (not markdown) with a 2000-character limit per text block. Page content is stored as blocks (use `GET /v1/blocks/$PAGE_ID/children`), not a single description field. Labels use multi-select properties. The Notion API does not support editing comments — post new comments instead.
1330
+ - Jira uses the new `POST /rest/api/3/search/jql` endpoint (old GET `/search` removed Aug 2025).