pm-slack 2026.6.2 → 2026.6.4-1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,6 +1,36 @@
1
1
  # Changelog
2
2
 
3
- ## 2026.6.2 - 2026-06-02
3
+ ## 2026.6.4-1 - 2026-06-04
4
+
5
+ ### Added
6
+
7
+ - preflight: fail-fast Slack webhook validation gate ([pm-slack-676l](https://github.com/unbraind/pm-slack/blob/main/.agents/pm/features/pm-slack-676l.toon))
8
+
9
+ ## 2026.06.04 - 2026-06-04
10
+
11
+ ### Added
12
+
13
+ - Lifecycle event taxonomy + assignee mentions + action buttons ([pm-slack-c3q7](https://github.com/unbraind/pm-slack/blob/main/.agents/pm/features/pm-slack-c3q7.toon))
14
+
15
+ ## 2026.06.03 - 2026-06-02
16
+
17
+ ### Added
18
+
19
+ - Deep feature enhancement 2026-06-03 ([pm-slack-l3ux](https://github.com/unbraind/pm-slack/blob/main/.agents/pm/features/pm-slack-l3ux.toon))
20
+ - Digest reads pm store toon/json files directly \(no new SDK service\) ([pm-slack-06ti](https://github.com/unbraind/pm-slack/blob/main/.agents/pm/decisions/pm-slack-06ti.toon))
21
+
22
+ ### Other
23
+
24
+ - Best-effort hook policy unchanged; strict creds only in test/digest real-post ([pm-slack-b08f](https://github.com/unbraind/pm-slack/blob/main/.agents/pm/decisions/pm-slack-b08f.toon))
25
+ - Routing model: PM\_SLACK\_ROUTES JSON map + per-event channel override ([pm-slack-i3ir](https://github.com/unbraind/pm-slack/blob/main/.agents/pm/decisions/pm-slack-i3ir.toon))
26
+ - Unit tests + README + functional test + release 2026.6.3 ([pm-slack-ffs4](https://github.com/unbraind/pm-slack/blob/main/.agents/pm/tasks/pm-slack-ffs4.toon))
27
+ - Strict missing-creds CommandError for real posts; keep best-effort hook ([pm-slack-7fk4](https://github.com/unbraind/pm-slack/blob/main/.agents/pm/tasks/pm-slack-7fk4.toon))
28
+ - slack digest command: activity summary over --since/--days ([pm-slack-nga4](https://github.com/unbraind/pm-slack/blob/main/.agents/pm/tasks/pm-slack-nga4.toon))
29
+ - slack test command: offline preview, no network, --json ([pm-slack-36xw](https://github.com/unbraind/pm-slack/blob/main/.agents/pm/tasks/pm-slack-36xw.toon))
30
+ - Event/type routing to multiple webhooks/channels \(PM\_SLACK\_ROUTES\) ([pm-slack-747x](https://github.com/unbraind/pm-slack/blob/main/.agents/pm/tasks/pm-slack-747x.toon))
31
+ - Format toggle: --format text\|blockkit for hook + commands ([pm-slack-1fbz](https://github.com/unbraind/pm-slack/blob/main/.agents/pm/tasks/pm-slack-1fbz.toon))
32
+
33
+ ## 2026.06.02 - 2026-06-02
4
34
 
5
35
  ### Added
6
36
 
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Slack notifications for [pm-cli](https://github.com/unbraind/pm-cli) item lifecycle events.
4
4
 
5
- Fires after `create`, `close`, and `block` operations and posts a formatted message to a Slack incoming webhook.
5
+ Fires after `create`, `close`, `block`, `cancel`, `open`, `start`, `unblock`, and `reopen` lifecycle transitions and posts a formatted message to a Slack incoming webhook. Optionally maps assignees to Slack `@mentions` and adds Block Kit action buttons linking to the item / GitHub URL.
6
6
 
7
7
  ---
8
8
 
@@ -33,10 +33,14 @@ pm install github.com/unbraind/pm-slack --project
33
33
 
34
34
  | Variable | Required | Default | Description |
35
35
  |---|---|---|---|
36
- | `PM_SLACK_WEBHOOK` | **Yes** | — | Slack incoming webhook URL |
37
- | `PM_SLACK_CHANNEL` | No | — | Channel hint appended to messages (e.g. `#pm-alerts`) |
36
+ | `PM_SLACK_WEBHOOK` | Usually | — | Slack incoming webhook URL (optional if every routing rule carries its own `webhook`) |
37
+ | `PM_SLACK_CHANNEL` | No | — | Default channel hint appended to messages (e.g. `#pm-alerts`) |
38
38
  | `PM_SLACK_MIN_PRIORITY` | No | `1` | Minimum priority to notify (1=critical, 2=high, 3=medium, 4=low) |
39
- | `PM_SLACK_EVENTS` | No | `create,close,block` | Comma-separated list of events to notify on |
39
+ | `PM_SLACK_EVENTS` | No | all events | Comma-separated list of events to notify on. Status-name aliases (`canceled`, `in_progress`, …) accepted |
40
+ | `PM_SLACK_FORMAT` | No | `blockkit` | Default message format: `blockkit` (rich) or `text` (plain mrkdwn) |
41
+ | `PM_SLACK_ROUTES` | No | — | JSON array of routing rules (see [Routing](#routing-by-event-type-or-status)) |
42
+ | `PM_SLACK_ASSIGNEE_MAP` | No | — | Comma list of `name=slackId` pairs mapping assignees to Slack `@mentions`, e.g. `alice=U123,bob=U456` |
43
+ | `PM_SLACK_MENTION_ASSIGNEE` | No | auto | Set `0`/`false` to disable assignee mentions even when a map is set (auto-enabled when `PM_SLACK_ASSIGNEE_MAP` is non-empty) |
40
44
 
41
45
  Export them in your shell profile or `.env`:
42
46
 
@@ -45,6 +49,7 @@ export PM_SLACK_WEBHOOK="<slack-webhook-url>"
45
49
  export PM_SLACK_CHANNEL="#pm-alerts"
46
50
  export PM_SLACK_MIN_PRIORITY=2 # only critical + high
47
51
  export PM_SLACK_EVENTS="create,close" # skip block notifications
52
+ export PM_SLACK_FORMAT="text" # plain text instead of Block Kit
48
53
  ```
49
54
 
50
55
  ---
@@ -55,7 +60,17 @@ export PM_SLACK_EVENTS="create,close" # skip block notifications
55
60
  |---|---|---|
56
61
  | `create` | `pm add`, `pm create`, `pm new` | Fires when a new item is created |
57
62
  | `close` | `pm close`, `pm done`, `pm complete` | Fires when an item is closed/resolved |
58
- | `block` | `pm update --status blocked`, `pm set status blocked` | Fires when an item's status changes to blocked |
63
+ | `block` | `pm update --status blocked` | Fires when an item's status changes to blocked |
64
+ | `cancel` | `pm update --status canceled` | Fires when an item is canceled (reason read from `close_reason`) |
65
+ | `open` | `pm update --status open` (e.g. `draft → open`) | Fires when an item is published/opened |
66
+ | `start` | `pm claim`, `pm start-task`, `pm update --status in_progress` | Fires when work begins on an item |
67
+ | `unblock` | `pm update --status open` from a `blocked` state | Distinguished when the result carries the prior status |
68
+ | `reopen` | `pm update --status open` from a `closed`/`canceled` state | Distinguished when the result carries the prior status |
69
+
70
+ `unblock` / `reopen` are refinements of an `open` transition: when pm's hook
71
+ result exposes the previous status, the more specific event is used; otherwise
72
+ the transition surfaces as `open`. Status-name aliases (`canceled`,
73
+ `in_progress`, `reopened`, …) are accepted anywhere an event name is.
59
74
 
60
75
  ---
61
76
 
@@ -115,12 +130,184 @@ PM_SLACK_EVENTS="create,close"
115
130
 
116
131
  ---
117
132
 
133
+ ## Message Formats
134
+
135
+ Notifications render as rich Slack **Block Kit** by default (header, a fields
136
+ grid with item id / type / event / priority / status / author, an optional
137
+ reason section, and a context footer). Set the format to plain `text` for
138
+ minimal/legacy channels:
139
+
140
+ ```bash
141
+ export PM_SLACK_FORMAT="text" # plain mrkdwn, no blocks
142
+ ```
143
+
144
+ Per-command, the `--format blockkit|text` flag overrides the env var for
145
+ `slack notify`, `slack test`, and `slack digest`.
146
+
147
+ ---
148
+
149
+ ## Routing (by event, type, or status)
150
+
151
+ `PM_SLACK_ROUTES` is an optional JSON array of rules that send specific events,
152
+ item types, or statuses to a different webhook and/or channel. Routing is purely
153
+ additive — with no rules configured, behavior is unchanged.
154
+
155
+ Each rule is `{ "match": "<selector>", "webhook"?: "...", "channel"?: "..." }`.
156
+ Selectors:
157
+
158
+ | Selector | Matches |
159
+ |---|---|
160
+ | `create` / `close` / `block` | that lifecycle event |
161
+ | `type:<itemType>` | items of that type (case-insensitive) |
162
+ | `status:<status>` | items in that status (case-insensitive) |
163
+ | `*` / `all` | everything (use as a catch-all) |
164
+
165
+ The **first matching rule wins**; any field a rule omits falls back to the
166
+ default `PM_SLACK_WEBHOOK` / `PM_SLACK_CHANNEL`.
167
+
168
+ ```bash
169
+ export PM_SLACK_ROUTES='[
170
+ { "match": "block", "channel": "#urgent" },
171
+ { "match": "type:Bug", "webhook": "https://hooks.slack.com/services/AAA/BBB/CCC" }
172
+ ]'
173
+ ```
174
+
175
+ ---
176
+
177
+ ## Assignee @mentions
178
+
179
+ Map a pm item's `assignee` to a Slack user (or group) id so notifications
180
+ @-mention the responsible person. Set `PM_SLACK_ASSIGNEE_MAP` to a comma list of
181
+ `name=id` pairs:
182
+
183
+ ```bash
184
+ export PM_SLACK_ASSIGNEE_MAP="alice=U0123ABC,bob=U0456DEF,team=<!subteam^S0789GHI>"
185
+ ```
186
+
187
+ - A raw id (`U0123ABC`) is wrapped as `<@U0123ABC>`.
188
+ - A pre-wrapped token (e.g. `<!subteam^S0789GHI>` for a group) is used verbatim.
189
+ - Mentions auto-enable in the hook whenever the map is non-empty; set
190
+ `PM_SLACK_MENTION_ASSIGNEE=0` to force them off.
191
+ - For ad-hoc posts, `pm slack notify --assignee alice --mention-assignee` resolves
192
+ the mention from the same map. When no mapping exists, the raw assignee name is
193
+ still shown (blockkit) — output is byte-identical to before when no map is set.
194
+
195
+ ---
196
+
197
+ ## Action buttons (item / GitHub links)
198
+
199
+ When a pm item carries a URL, the Block Kit notification adds an action button
200
+ linking to it (`View on GitHub` for github.com URLs, otherwise `Open item`). The
201
+ URL is read from the first present of these item fields: `github_url`,
202
+ `html_url`, `url`, `source_url`, `link` (camelCase variants accepted). The
203
+ plain-`text` format is unchanged. For ad-hoc posts:
204
+
205
+ ```bash
206
+ pm slack notify --title 'Deploy' --on close --url 'https://github.com/unbraind/pm-slack/pull/3' --dry-run
207
+ ```
208
+
209
+ ---
210
+
211
+ ## Commands
212
+
213
+ ### `pm slack notify`
214
+
215
+ Manually post a Block Kit (or text) message for an ad-hoc note.
216
+
217
+ ```bash
218
+ pm slack notify --text 'Release shipped :rocket:' --dry-run
219
+ pm slack notify --title 'Deploy done' --on close --format text --channel '#releases'
220
+ ```
221
+
222
+ ### `pm slack test`
223
+
224
+ Build and **print** a sample notification in the chosen format **without
225
+ posting** — fully offline, no webhook required. Great for previewing formatting.
226
+
227
+ ```bash
228
+ pm slack test --format blockkit # rich preview
229
+ pm slack test --format text --on close # plain-text preview of a close event
230
+ pm slack test --on block --json # machine-readable payload (with global --json)
231
+ ```
232
+
233
+ Flags: `--format blockkit|text`, `--on create|close|block`, `--title`,
234
+ `--channel`, `--json`.
235
+
236
+ ### `pm slack digest`
237
+
238
+ Produce a single summary of recent activity (created / closed / blocked /
239
+ in-progress) over a time window, as Block Kit or text. Reads the pm store
240
+ directly. Use `--dry-run` to preview without posting.
241
+
242
+ ```bash
243
+ pm slack digest --days 7 --dry-run
244
+ pm slack digest --since 2026-06-01 --format text --dry-run
245
+ pm slack digest --days 1 --format blockkit # real post (needs a webhook)
246
+ ```
247
+
248
+ Flags: `--since <date>`, `--days <n>` (default 7), `--format blockkit|text`,
249
+ `--channel`, `--webhook`, `--dry-run`, `--json`.
250
+
251
+ A real (non-`--dry-run`) `slack digest` post without a configured webhook fails
252
+ with a clear error (exit 1) rather than crashing.
253
+
254
+ ---
255
+
256
+ ## Webhook preflight gate
257
+
258
+ The Slack-posting commands (`slack notify`, `slack digest`) run a **fail-fast
259
+ preflight** that validates webhook configuration **before** anything is posted:
260
+
261
+ - The effective webhook is resolved with precedence `--webhook` flag →
262
+ `PM_SLACK_WEBHOOK` → a per-rule webhook in `PM_SLACK_ROUTES`.
263
+ - If no webhook is configured, or the configured URL is not a valid `http(s)`
264
+ URL, the command **aborts immediately** with a clear, actionable error and a
265
+ **non-zero exit** (`2`, usage error) — nothing is posted, no network call is
266
+ made.
267
+ - On a valid configuration the gate is a **silent pass-through**.
268
+
269
+ Validation is **purely syntactic/environment-based — no network call**, so it is
270
+ cheap and offline-safe. It does *not* verify the webhook actually works; a valid
271
+ but dead webhook still passes the gate and surfaces at the network layer.
272
+
273
+ What is **not** gated:
274
+
275
+ - `slack test` — an offline preview that never posts and works with no webhook.
276
+ - `--dry-run` on `slack notify` / `slack digest` — also an offline preview.
277
+
278
+ The gate is registered both as a scoped `registerPreflight` override (for the
279
+ documented `preflight` capability and a visible warning) and — because the pm
280
+ runtime treats errors thrown from a preflight override as non-fatal — enforced in
281
+ the command handlers themselves, which throw a `CommandError` that genuinely
282
+ aborts the command.
283
+
284
+ ```bash
285
+ # no webhook configured:
286
+ pm slack notify --text 'hi' # → error + exit 2, nothing posted
287
+ pm slack notify --text 'hi' --dry-run # → offline preview, exit 0
288
+ pm slack test --on close # → offline preview, exit 0
289
+ ```
290
+
291
+ ---
292
+
118
293
  ## Error Handling
119
294
 
120
- - If `PM_SLACK_WEBHOOK` is not set, the extension silently skips all notifications and logs a debug message.
121
- - If the webhook URL is invalid, the extension logs an error and continues without crashing pm-cli.
122
- - HTTP errors from Slack (non-2xx responses) are logged but do not fail the pm-cli command.
123
- - Network timeouts are set to 10 seconds.
295
+ The **lifecycle hook is best-effort** and never breaks your pm command:
296
+
297
+ - If `PM_SLACK_WEBHOOK` is not set, the hook silently skips all notifications and logs a debug message.
298
+ - If the webhook URL is invalid, the hook logs an error and continues without crashing pm-cli.
299
+ - HTTP errors from Slack (non-2xx responses), network failures, and timeouts (10s) are logged but never fail the underlying `pm` command.
300
+
301
+ The **`slack notify` and `slack digest` commands fail fast** on misconfiguration
302
+ (see [Webhook preflight gate](#webhook-preflight-gate)):
303
+
304
+ - A real (non-`--dry-run`) post without a configured webhook, or with an invalid
305
+ webhook URL, fails with a clear `CommandError` and a non-zero exit **before**
306
+ any post is attempted.
307
+ - Use `--dry-run` to build and preview with no webhook and no network call.
308
+ - Once past the gate, `slack notify` treats a *network/HTTP* failure as
309
+ best-effort (warns, exits 0); `slack digest` reports a post failure as an
310
+ error (exit 1).
124
311
 
125
312
  ---
126
313
 
@@ -141,12 +328,11 @@ The extension uses only Node.js built-ins (`node:https`) — no external runtime
141
328
  ```json
142
329
  {
143
330
  "name": "pm-slack",
144
- "version": "0.1.0",
145
331
  "description": "Slack notifications for pm item lifecycle events",
146
332
  "author": "@unbraind",
147
- "entry": "index.js",
333
+ "entry": "./dist/index.js",
148
334
  "priority": 50,
149
- "capabilities": ["hooks", "services"]
335
+ "capabilities": ["commands", "hooks", "schema", "preflight"]
150
336
  }
151
337
  ```
152
338
 
package/dist/index.d.ts CHANGED
@@ -13,7 +13,20 @@
13
13
  * PM_SLACK_WEBHOOK (required) Slack incoming webhook URL
14
14
  * PM_SLACK_CHANNEL (optional) Override channel, e.g. #pm-alerts
15
15
  * PM_SLACK_MIN_PRIORITY (optional) Minimum priority to notify (1=critical … 4=low), default 1 (critical only; set 4 for all)
16
- * PM_SLACK_EVENTS (optional) Comma-separated subset of hook events: create,close,block (default: all)
16
+ * PM_SLACK_EVENTS (optional) Comma-separated subset of hook events:
17
+ * create,close,block,cancel,open,start,unblock,reopen (default: all).
18
+ * Status-name aliases (e.g. "canceled","in_progress") are accepted.
19
+ * PM_SLACK_FORMAT (optional) Default notification format: "blockkit" (default) or "text"
20
+ * PM_SLACK_ASSIGNEE_MAP (optional) Comma list of "name=slackId" pairs mapping a pm item's
21
+ * assignee to a Slack @mention, e.g. "alice=U123,bob=U456".
22
+ * PM_SLACK_MENTION_ASSIGNEE (optional) Set to 0/false to disable assignee @mentions even when a map is set
23
+ * (mentions auto-enable in the hook whenever PM_SLACK_ASSIGNEE_MAP is non-empty).
24
+ * PM_SLACK_ROUTES (optional) JSON array of routing rules to send specific
25
+ * events/types/statuses to different webhooks/channels.
26
+ * Each rule: { "match": "<selector>", "webhook"?: "...", "channel"?: "..." }
27
+ * Selector forms: an event ("create"|"close"|"block"),
28
+ * "type:<itemType>" (case-insensitive), or "status:<status>".
29
+ * First matching rule wins; unset fields fall back to the defaults.
17
30
  */
18
31
  import type { AfterCommandHookContext } from "@unbrained/pm-cli/sdk";
19
32
  declare class CommandError extends Error {
@@ -31,25 +44,182 @@ interface PmItem {
31
44
  blocked_reason?: string;
32
45
  closedReason?: string;
33
46
  blockedReason?: string;
47
+ cancel_reason?: string;
48
+ cancelReason?: string;
34
49
  author?: string;
50
+ assignee?: string;
51
+ url?: string;
52
+ github_url?: string;
53
+ githubUrl?: string;
54
+ html_url?: string;
55
+ htmlUrl?: string;
56
+ source_url?: string;
57
+ sourceUrl?: string;
58
+ link?: string;
59
+ }
60
+ type EventKind = "create" | "close" | "block" | "cancel" | "open" | "start" | "unblock" | "reopen";
61
+ type MessageFormat = "blockkit" | "text";
62
+ /** A single routing rule parsed from PM_SLACK_ROUTES. */
63
+ interface RouteRule {
64
+ /** Raw selector string, e.g. "block", "type:Bug", "status:blocked". */
65
+ match: string;
66
+ webhook?: string;
67
+ channel?: string;
68
+ }
69
+ /** Resolved destination for a notification after applying routing rules. */
70
+ interface RouteTarget {
71
+ webhookUrl: string;
72
+ channel?: string;
35
73
  }
36
- type EventKind = "create" | "close" | "block";
37
74
  interface SlackBlock {
38
75
  type: string;
39
76
  [key: string]: unknown;
40
77
  }
78
+ interface SlackPayload {
79
+ text: string;
80
+ blocks?: SlackBlock[];
81
+ mrkdwn?: boolean;
82
+ channel?: string;
83
+ thread_ts?: string;
84
+ }
85
+ declare function normalizeEvent(token: string): EventKind | null;
41
86
  declare function parseEvents(spec: string | undefined): Set<EventKind>;
87
+ /**
88
+ * Normalize a format spec to a known MessageFormat. Accepts a few friendly
89
+ * aliases ("block"/"blocks" → blockkit, "plain"/"txt" → text). Falls back to
90
+ * the supplied default when the spec is empty or unrecognized.
91
+ */
92
+ declare function parseFormat(spec: string | undefined, fallback?: MessageFormat): MessageFormat;
93
+ declare function parseRoutes(spec: string | undefined): RouteRule[];
94
+ /**
95
+ * Does a single route rule match this event/item? Selector forms:
96
+ * - bare event name: "create" | "close" | "block"
97
+ * - "type:<itemType>" (case-insensitive on item.type)
98
+ * - "status:<status>" (case-insensitive on item.status)
99
+ * - "*" / "all" (matches everything — useful as a catch-all)
100
+ */
101
+ declare function ruleMatches(rule: RouteRule, event: EventKind, item: PmItem): boolean;
102
+ /**
103
+ * Resolve the destination (webhook + channel) for an event/item given the
104
+ * configured routes and the default webhook/channel. First matching rule wins;
105
+ * any field a rule omits falls back to the default. Returns null only when no
106
+ * webhook can be resolved at all.
107
+ */
108
+ declare function selectRoute(event: EventKind, item: PmItem, routes: RouteRule[], defaultWebhook: string, defaultChannel?: string): RouteTarget | null;
109
+ /**
110
+ * Resolve the effective webhook for a posting command from an explicit
111
+ * `--webhook` flag (highest precedence) or PM_SLACK_WEBHOOK. PM_SLACK_ROUTES
112
+ * may also carry per-rule webhooks; a present route webhook is accepted as a
113
+ * valid source so route-only configurations are not falsely rejected.
114
+ */
115
+ declare function resolveEffectiveWebhook(webhookFlag: string | undefined): {
116
+ webhookUrl: string;
117
+ source: "flag" | "env" | "route" | "none";
118
+ };
119
+ /**
120
+ * Validate webhook configuration for a Slack-posting command. Throws a
121
+ * CommandError (exit 2 / USAGE) with an actionable message when no webhook is
122
+ * configured or the configured webhook URL is syntactically invalid. Returns
123
+ * silently (pass-through) on a valid config. Performs NO network I/O.
124
+ *
125
+ * `commandLabel` is woven into the message (e.g. "slack digest") so the user
126
+ * sees exactly which command was gated.
127
+ */
128
+ declare function assertWebhookConfigured(webhookFlag: string | undefined, commandLabel: string): void;
42
129
  declare function meetsMinPriority(item: PmItem, minPriority: Priority): boolean;
43
- declare function buildTextMessage(item: PmItem, event: EventKind, channel?: string): string;
130
+ /** Parse PM_SLACK_ASSIGNEE_MAP into a case-insensitive name→id lookup. */
131
+ declare function parseAssigneeMap(spec: string | undefined): Map<string, string>;
132
+ /**
133
+ * Resolve an item's assignee to a Slack mention token using the map. Returns a
134
+ * ready-to-embed mention string (e.g. `<@U123>`) or undefined when there's no
135
+ * assignee or no mapping. A raw id is wrapped as `<@id>`; a token already
136
+ * containing `<` is passed through unchanged (supports `<!subteam^S123>` etc).
137
+ */
138
+ declare function resolveAssigneeMention(item: PmItem, map: Map<string, string>): string | undefined;
139
+ declare function isHttpUrl(value: unknown): value is string;
140
+ /** Resolve a primary URL for the item, plus whether it points at GitHub. */
141
+ declare function resolveItemUrl(item: PmItem, override?: string): {
142
+ url: string;
143
+ isGithub: boolean;
144
+ } | undefined;
145
+ declare function buildTextMessage(item: PmItem, event: EventKind, channel?: string, mention?: string): string;
44
146
  interface BlockKitOptions {
45
147
  channel?: string;
46
148
  /** Extra free-form body appended as a section (e.g. from `--text`). */
47
149
  note?: string;
150
+ /** Pre-resolved Slack mention token for the assignee (e.g. `<@U123>`). */
151
+ mention?: string;
152
+ /** Resolved item/GitHub URL for an action button. */
153
+ link?: {
154
+ url: string;
155
+ isGithub: boolean;
156
+ };
48
157
  }
49
158
  declare function buildItemBlockKit(item: PmItem, event: EventKind, opts?: BlockKitOptions): {
50
159
  blocks: SlackBlock[];
51
160
  fallback: string;
52
161
  };
162
+ /**
163
+ * Build a ready-to-post SlackPayload for an item event in the requested format.
164
+ * - "blockkit": rich Block Kit blocks + plain-text fallback in `text`.
165
+ * - "text": plain mrkdwn only (no `blocks`), for minimal/legacy channels.
166
+ */
167
+ declare function buildItemPayload(item: PmItem, event: EventKind, format: MessageFormat, opts?: BlockKitOptions): SlackPayload;
168
+ /** Fields we read off each stored item for digest purposes. */
169
+ interface DigestItem extends PmItem {
170
+ created_at?: string;
171
+ updated_at?: string;
172
+ }
173
+ /**
174
+ * Minimal parser for a stored pm item file. Handles the toon scalar form
175
+ * (`key: value`, optionally quoted) and JSON. Only top-level scalar fields are
176
+ * extracted; nested/array sections are ignored. Returns null when no id found.
177
+ */
178
+ declare function parseStoredItem(content: string, ext: string): DigestItem | null;
179
+ /**
180
+ * Resolve a `--since <date>` / `--days <n>` window into an epoch-ms cutoff.
181
+ * `since` (ISO date or datetime) wins when both are given; otherwise `days`
182
+ * back from `now`. Defaults to 7 days. Returns the cutoff in ms.
183
+ */
184
+ declare function resolveWindow(since: string | undefined, days: number | undefined, now?: number): {
185
+ cutoffMs: number;
186
+ label: string;
187
+ };
188
+ type DigestBucket = "created" | "closed" | "blocked" | "in_progress";
189
+ interface DigestSummary {
190
+ windowLabel: string;
191
+ cutoffMs: number;
192
+ counts: Record<DigestBucket, number>;
193
+ buckets: Record<DigestBucket, DigestItem[]>;
194
+ total: number;
195
+ }
196
+ /**
197
+ * Aggregate store items into digest buckets for the window. An item counts as:
198
+ * - created if created_at >= cutoff
199
+ * - closed if it is in a closed/done status AND updated_at >= cutoff
200
+ * - blocked if status === blocked AND updated_at >= cutoff
201
+ * - in_progress if status === in_progress AND updated_at >= cutoff
202
+ * An item may appear in multiple buckets (e.g. created and blocked).
203
+ */
204
+ declare function aggregateDigest(items: DigestItem[], cutoffMs: number, windowLabel: string): DigestSummary;
205
+ declare function buildDigestText(summary: DigestSummary, channel?: string): string;
206
+ declare function buildDigestBlockKit(summary: DigestSummary, channel?: string): {
207
+ blocks: SlackBlock[];
208
+ fallback: string;
209
+ };
210
+ declare function buildDigestPayload(summary: DigestSummary, format: MessageFormat, channel?: string): SlackPayload;
211
+ /** Map a (normalized) status string to the EventKind for a transition into it. */
212
+ declare function statusToEvent(status: string | undefined): EventKind | null;
213
+ /**
214
+ * Detect the lifecycle event for a completed command. Strategy:
215
+ * 1. Create/close commands map directly.
216
+ * 2. Status-changing commands (update/set/lifecycle aliases) derive the event
217
+ * from the *resulting* status (preferring the explicit --status option,
218
+ * falling back to the result item's status).
219
+ * 3. When the result carries the prior status (`previousStatus`), refine
220
+ * open→reopen (from a closed/canceled state) and open→unblock (from
221
+ * blocked) so terminal-recovery transitions are distinguishable.
222
+ */
53
223
  declare function detectEvent(ctx: AfterCommandHookContext): EventKind | null;
54
224
  declare function extractItem(ctx: AfterCommandHookContext): PmItem | null;
55
225
  declare const _default: {
@@ -60,11 +230,34 @@ declare const _default: {
60
230
  export default _default;
61
231
  export declare const __test__: {
62
232
  buildItemBlockKit: typeof buildItemBlockKit;
233
+ buildItemPayload: typeof buildItemPayload;
63
234
  buildTextMessage: typeof buildTextMessage;
64
235
  parseEvents: typeof parseEvents;
236
+ normalizeEvent: typeof normalizeEvent;
237
+ parseFormat: typeof parseFormat;
238
+ parseRoutes: typeof parseRoutes;
239
+ ruleMatches: typeof ruleMatches;
240
+ selectRoute: typeof selectRoute;
65
241
  detectEvent: typeof detectEvent;
242
+ statusToEvent: typeof statusToEvent;
66
243
  extractItem: typeof extractItem;
67
244
  meetsMinPriority: typeof meetsMinPriority;
245
+ parseAssigneeMap: typeof parseAssigneeMap;
246
+ resolveAssigneeMention: typeof resolveAssigneeMention;
247
+ resolveItemUrl: typeof resolveItemUrl;
248
+ isHttpUrl: typeof isHttpUrl;
249
+ EVENT_META: Record<EventKind, {
250
+ verb: string;
251
+ emoji: string;
252
+ }>;
253
+ parseStoredItem: typeof parseStoredItem;
254
+ resolveWindow: typeof resolveWindow;
255
+ aggregateDigest: typeof aggregateDigest;
256
+ buildDigestText: typeof buildDigestText;
257
+ buildDigestBlockKit: typeof buildDigestBlockKit;
258
+ buildDigestPayload: typeof buildDigestPayload;
259
+ resolveEffectiveWebhook: typeof resolveEffectiveWebhook;
260
+ assertWebhookConfigured: typeof assertWebhookConfigured;
68
261
  EXIT_CODE: {
69
262
  readonly GENERIC_FAILURE: 1;
70
263
  readonly USAGE: 2;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAGH,OAAO,KAAK,EAEV,uBAAuB,EAExB,MAAM,uBAAuB,CAAC;AAqB/B,cAAM,YAAa,SAAQ,KAAK;IAC9B,QAAQ,EAAE,MAAM,CAAC;gBACL,OAAO,EAAE,MAAM,EAAE,QAAQ,GAAE,MAAkC;CAK1E;AAMD,KAAK,QAAQ,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;AAE9B,UAAU,MAAM;IACd,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;IAGhB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,KAAK,SAAS,GAAG,QAAQ,GAAG,OAAO,GAAG,OAAO,CAAC;AAE9C,UAAU,UAAU;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAmDD,iBAAS,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,GAAG,CAAC,SAAS,CAAC,CAO7D;AAsDD,iBAAS,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,QAAQ,GAAG,OAAO,CAItE;AA0BD,iBAAS,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAalF;AAUD,UAAU,eAAe;IACvB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,uEAAuE;IACvE,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,iBAAS,iBAAiB,CACxB,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,SAAS,EAChB,IAAI,GAAE,eAAoB,GACzB;IAAE,MAAM,EAAE,UAAU,EAAE,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CA+C5C;AA+DD,iBAAS,WAAW,CAAC,GAAG,EAAE,uBAAuB,GAAG,SAAS,GAAG,IAAI,CAkBnE;AAED,iBAAS,WAAW,CAAC,GAAG,EAAE,uBAAuB,GAAG,MAAM,GAAG,IAAI,CAehE;;;;;;AAMD,wBAoLG;AAGH,eAAO,MAAM,QAAQ;;;;;;;;;;;;;CASpB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"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"}