@danypops/tickets 0.5.0 → 0.5.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@danypops/tickets",
3
- "version": "0.5.0",
3
+ "version": "0.5.2",
4
4
  "description": "Unified CLI, daemon, and TypeScript library for issue tracking across GitHub, GitLab, and Jira.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -104,7 +104,10 @@ interface JiraRemoteLink {
104
104
  }
105
105
  interface JiraComment {
106
106
  id?: string;
107
- comment?: string;
107
+ // The real Jira REST v2 wire field is "body" (confirmed against Atlassian's own
108
+ // docs/example payloads). jira.js's bundled Models.Comment type mistypes this as
109
+ // "comment" -- a library type/runtime mismatch, not an ADF-rendering issue.
110
+ body?: string;
108
111
  created?: string;
109
112
  updated?: string;
110
113
  author?: { displayName?: string };
@@ -295,6 +298,24 @@ export class JiraRepository {
295
298
  return result.jql;
296
299
  }
297
300
 
301
+ /**
302
+ * BoardFilterDiscoverable -- resolves a board's own real base scope: its saved
303
+ * filter's JQL, via `GET /rest/agile/1.0/board/{boardId}/configuration` (filter
304
+ * id) then `GET /rest/api/2/filter/{id}` (that filter's JQL). This is the actual
305
+ * project/query scope a board tracks -- assuming a board scopes to a single named
306
+ * project (e.g. matching a URL path segment) is a guess, not a fact; a project can
307
+ * be renamed or a board's filter can span multiple projects, so this is always
308
+ * resolved from Jira, never inferred from a project key string.
309
+ */
310
+ async discoverBoardFilterJql(boardId: number): Promise<string> {
311
+ const config = await this.call<{ filter?: { id?: string } }>(() => this.agileClient().board.getConfiguration({ boardId }));
312
+ const filterId = config?.filter?.id;
313
+ if (!filterId) throw new Error(`jira: board ${boardId} has no configured filter`);
314
+ const filter = await this.call<{ jql?: string }>(() => this.client.filters.getFilter({ id: Number(filterId) }));
315
+ if (!filter?.jql) throw new Error(`jira: filter ${filterId} (board ${boardId}'s own filter) has no JQL`);
316
+ return filter.jql;
317
+ }
318
+
298
319
  private agileClientInstance?: AgileClient;
299
320
 
300
321
  private agileClient(): AgileClient {
@@ -607,7 +628,7 @@ function mapPriorityFromJira(name: string | undefined): ReturnType<typeof parseP
607
628
  function commentToDomain(c: JiraComment): Comment {
608
629
  return {
609
630
  id: c.id ?? "",
610
- body: c.comment ?? "",
631
+ body: c.body ?? "",
611
632
  author: c.author?.displayName,
612
633
  createdAt: c.created,
613
634
  updatedAt: c.updated,
@@ -8,6 +8,7 @@ import type { Comment, CreateInput, Issue, ListFilter, UpdateInput } from "../do
8
8
  import { parseRef } from "../domain/issue.js";
9
9
  import type { Template } from "../domain/template.js";
10
10
  import {
11
+ hasBoardFilterDiscovery,
11
12
  hasBoardQuickFilterDiscovery,
12
13
  hasComments,
13
14
  hasFieldDiscovery,
@@ -133,4 +134,11 @@ export class TicketService {
133
134
  if (!hasBoardQuickFilterDiscovery(repo)) throw new NotSupportedError(backend, "board quick filter discovery");
134
135
  return repo.discoverBoardQuickFilterJql(boardId, quickFilterId);
135
136
  }
137
+
138
+ /** Resolves a Jira board's own real base scope (its saved filter's JQL) -- never assume a board tracks one named project. */
139
+ async discoverBoardFilterJql(backend: string, boardId: number): Promise<string> {
140
+ const repo = this.repo(backend);
141
+ if (!hasBoardFilterDiscovery(repo)) throw new NotSupportedError(backend, "board filter discovery");
142
+ return repo.discoverBoardFilterJql(boardId);
143
+ }
136
144
  }
package/src/cli/index.ts CHANGED
@@ -230,6 +230,15 @@ discoverCmd
230
230
  );
231
231
  });
232
232
 
233
+ discoverCmd
234
+ .command("board_filter")
235
+ .description("resolve a Jira board's own real base scope -- its saved filter's JQL -- instead of assuming it tracks one named project")
236
+ .requiredOption("-b, --backend <name>", "backend name")
237
+ .requiredOption("--board <id>", "board id", (v) => Number.parseInt(v, 10))
238
+ .action(async (opts) => {
239
+ await withClient((client) => client.call("discover.board_filter", { backend: opts.backend, boardId: opts.board }));
240
+ });
241
+
233
242
  const queryCmd = program.command("query").description("save and run named raw backend queries (Jira JQL) -- e.g. a board's sprint or backlog view");
234
243
 
235
244
  queryCmd
package/src/daemon/ops.ts CHANGED
@@ -30,6 +30,7 @@ export type TicketOperation =
30
30
  | "discover.statuses"
31
31
  | "discover.template"
32
32
  | "discover.board_quickfilter"
33
+ | "discover.board_filter"
33
34
  | "query.save"
34
35
  | "query.list"
35
36
  | "query.remove"
@@ -57,6 +58,7 @@ export interface TicketOpInputs extends Record<TicketOperation, unknown> {
57
58
  "discover.statuses": { backend: string };
58
59
  "discover.template": { backend: string; project: string; issueType: string; sampleSize?: number };
59
60
  "discover.board_quickfilter": { backend: string; boardId: number; quickFilterId: number };
61
+ "discover.board_filter": { backend: string; boardId: number };
60
62
  "query.save": { name: string; backend: string; query: string; description?: string };
61
63
  "query.list": Record<string, never>;
62
64
  "query.remove": { name: string };
@@ -85,6 +87,7 @@ export interface TicketOpOutputs extends Record<TicketOperation, unknown> {
85
87
  "discover.statuses": { mappings: Record<string, string> };
86
88
  "discover.template": { template: Template | null };
87
89
  "discover.board_quickfilter": { jql: string };
90
+ "discover.board_filter": { jql: string };
88
91
  "query.save": { query: SavedQuery };
89
92
  "query.list": { queries: SavedQuery[] };
90
93
  "query.remove": { removed: boolean };
@@ -113,6 +116,7 @@ export const TICKET_OPERATIONS: TicketOperation[] = [
113
116
  "discover.statuses",
114
117
  "discover.template",
115
118
  "discover.board_quickfilter",
119
+ "discover.board_filter",
116
120
  "query.save",
117
121
  "query.list",
118
122
  "query.remove",
@@ -94,6 +94,7 @@ export const TICKET_OP_HANDLERS: { [Op in TicketOperation]: Handler<Op> } = {
94
94
  "discover.board_quickfilter": async (deps, input) => ({
95
95
  jql: await deps.service.discoverBoardQuickFilterJql(input.backend, input.boardId, input.quickFilterId),
96
96
  }),
97
+ "discover.board_filter": async (deps, input) => ({ jql: await deps.service.discoverBoardFilterJql(input.backend, input.boardId) }),
97
98
  "query.save": async (deps, input) => ({ query: deps.queries.save(input.name, input.backend, input.query, input.description) }),
98
99
  "query.list": async (deps) => ({ queries: deps.queries.list() }),
99
100
  "query.remove": async (deps, input) => ({ removed: deps.queries.remove(input.name) }),
@@ -88,3 +88,15 @@ export interface BoardQuickFilterDiscoverable {
88
88
  export function hasBoardQuickFilterDiscovery(repo: IssueRepository): repo is IssueRepository & BoardQuickFilterDiscoverable {
89
89
  return typeof (repo as Partial<BoardQuickFilterDiscoverable>).discoverBoardQuickFilterJql === "function";
90
90
  }
91
+
92
+ /**
93
+ * Optional capability — resolves a Jira board's own real base scope (its saved
94
+ * filter's JQL) rather than assuming a board tracks one named project. Jira only.
95
+ */
96
+ export interface BoardFilterDiscoverable {
97
+ discoverBoardFilterJql(boardId: number): Promise<string>;
98
+ }
99
+
100
+ export function hasBoardFilterDiscovery(repo: IssueRepository): repo is IssueRepository & BoardFilterDiscoverable {
101
+ return typeof (repo as Partial<BoardFilterDiscoverable>).discoverBoardFilterJql === "function";
102
+ }
@@ -112,6 +112,13 @@ const OPERATIONS: readonly OperationSpec[] = [
112
112
  properties: { backend: stringProp, boardId: numberProp, quickFilterId: numberProp },
113
113
  required: ["backend", "boardId", "quickFilterId"],
114
114
  },
115
+ {
116
+ action: "discover.board_filter",
117
+ description: "Resolves a Jira board's own real base scope -- its saved filter's JQL -- rather than assuming it tracks one named project.",
118
+ effect: "read",
119
+ properties: { backend: stringProp, boardId: numberProp },
120
+ required: ["backend", "boardId"],
121
+ },
115
122
  {
116
123
  action: "query.save",
117
124
  description: "Saves a raw backend query (Jira JQL) under a name, so it can be run again later without retyping it -- e.g. a board's sprint or backlog view.",