@thehammer/danx-dashboard-mcp 0.1.31 → 0.1.32

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/dist/handlers.js CHANGED
@@ -24,45 +24,40 @@
24
24
  import { readFile } from "node:fs/promises";
25
25
  import { basename, extname, isAbsolute } from "node:path";
26
26
  import { resolvePriority } from "./priority.js";
27
+ /**
28
+ * `filter`/`fields`/`sort` are JSON/CSV-encoded onto the query string (the
29
+ * route parses `?filter=<JSON>`, `?fields=a,b`, `?sort=<JSON>` — see
30
+ * `src/issues/read/reader.ts#parseEnvelope`); each is omitted entirely when
31
+ * empty so the wire carries no `{}`/`[]` noise. `board` stays a flat
32
+ * top-level param, resolved by the HTTP client exactly as before.
33
+ */
27
34
  export async function issueList(client, args) {
28
- // status_derived is display-only on the server but the route accepts
29
- // it as a projection filter — passthrough verbatim. parent_id=null is
30
- // the legitimate "root cards only" filter; the server reader treats
31
- // the literal string "null" identically.
32
35
  const query = {};
33
- if (args.type !== undefined)
34
- query.type = args.type;
35
- if (args.parent_id !== undefined) {
36
- query.parent_id = args.parent_id === null ? "null" : args.parent_id;
36
+ if (args.filter !== undefined && Object.keys(args.filter).length > 0) {
37
+ query.filter = JSON.stringify(args.filter);
38
+ }
39
+ if (args.fields !== undefined && args.fields.length > 0) {
40
+ query.fields = args.fields.join(",");
37
41
  }
38
- if (args.dispatchable_derived !== undefined) {
39
- query.dispatchable_derived = args.dispatchable_derived;
42
+ if (args.sort !== undefined && args.sort.length > 0) {
43
+ query.sort = JSON.stringify(args.sort);
40
44
  }
41
- if (args.assigned_agent !== undefined)
42
- query.assigned_agent = args.assigned_agent;
43
- if (args.include_closed !== undefined)
44
- query.include_closed = args.include_closed;
45
- if (args.status_derived !== undefined)
46
- query.status_derived = args.status_derived;
47
- if (args.q !== undefined)
48
- query.q = args.q;
49
45
  if (args.limit !== undefined)
50
46
  query.limit = args.limit;
51
47
  if (args.offset !== undefined)
52
48
  query.offset = args.offset;
53
- // DX-1163 — the agent surface is lean by default: opt the route into the
54
- // `IssueListRowLeanV2` projection (id/type/title/status/parent_id/
55
- // assigned_agent/children-ids/priority). The SPA omits this and keeps the
56
- // rich board row; `issue_get` still returns the full card body.
57
- query.lean = true;
58
49
  // DX-1171 — board-only: forward the qualified board id, no repo.
59
50
  return client.request({ method: "GET", path: "", query, board: args.board });
60
51
  }
61
- // ---------------- issue_get ----------------
62
52
  export async function issueGet(client, args) {
53
+ const query = {};
54
+ if (args.fields !== undefined && args.fields.length > 0) {
55
+ query.fields = args.fields.join(",");
56
+ }
63
57
  return client.request({
64
58
  method: "GET",
65
59
  path: `/${encodeURIComponent(args.id)}`,
60
+ query,
66
61
  board: args.board,
67
62
  });
68
63
  }
package/dist/index.js CHANGED
@@ -127,6 +127,36 @@ const EFFORT_VALUES = [
127
127
  ];
128
128
  const ISSUE_TYPES = ["Epic", "Bug", "Feature", "Story", "Chore"];
129
129
  const NON_EPIC_TYPES = ["Bug", "Feature", "Story", "Chore"];
130
+ // DX-935 / DX-937 — field-group taxonomy for the nested read envelope,
131
+ // hand-copied from `src/issues/read/field-groups.ts` (LIST_GROUPS / GET_GROUPS
132
+ // — this package cannot import server source). Drift surfaces at runtime as a
133
+ // server 400, not silently.
134
+ const LIST_FIELD_GROUPS = [
135
+ "description",
136
+ "ac",
137
+ "comments",
138
+ "retro",
139
+ "dependencies",
140
+ "triage",
141
+ "requires_human",
142
+ "assignment",
143
+ "quality_gates",
144
+ "children",
145
+ "effort",
146
+ ];
147
+ const GET_FIELD_GROUPS = [
148
+ ...LIST_FIELD_GROUPS,
149
+ "mirrors",
150
+ "code_review_items",
151
+ ];
152
+ const SORT_ORDERS = ["asc", "desc"];
153
+ const sortField = z
154
+ .array(z.object({
155
+ column: z.string().min(1),
156
+ order: z.enum(SORT_ORDERS),
157
+ }))
158
+ .optional()
159
+ .describe("Multi-column sort — ordered list of {column, order}. Absent → the server's default order (priority desc, repo_name asc, with a numeric-id tiebreaker always appended).");
130
160
  // DX-1290 — the uniform 4-state checklist-item status. Terminal = passing|cancelled.
131
161
  const CHECKLIST_ITEM_STATUSES = [
132
162
  "incomplete",
@@ -162,21 +192,37 @@ const boardField = {
162
192
  .describe("Target another board by its qualified id `<repo>:<slug>` (e.g. `platform:the-supply-operations-hub`); omit to use this dispatch's board. Unknown board → 404."),
163
193
  };
164
194
  // ---------------- issue_list ----------------
165
- server.tool("issue_list", "List issues for the dispatch's board by default via GET /api/issues. Board-scoped; defaults to the dispatch's board. Pass `board` (a qualified id `<repo>:<slug>`) to list another board instead (unknown board → 404). Filters: q (free-text search matched case-insensitively over title + descriptionuse this to find cards by keyword), type, parent_id (string id, null for root-only), dispatchable_derived (booleanserver-computed pickup-ready gate), assigned_agent, include_closed (default false excludes completed_at/cancelled_at). Returns ALL matching cards (no row cap unless you pass limit). Response body shape: {issues: Issue[]} LEAN agent rows (DX-1163): each row carries ONLY {id, type, title, status, parent_id, assigned_agent, children (ids), priority}. UI-only board fields (children_detail, counts, triage_ice_total, blocked_descendants, conflict_on, child_assignments, requires_human_child_count, latest_work_dispatch, primary_attachment) and the `description` body are NOT includedcall issue_get for the full card. Server-side numeric-suffix ordering so DX-10 follows DX-9. Use this instead of grepping .danxbot/issues/ the DB-backed route is the source of truth post-DX-704.", {
166
- q: z.string().optional(),
167
- status_derived: z.string().optional(),
168
- type: z.enum(ISSUE_TYPES).optional(),
169
- parent_id: z.string().nullable().optional(),
170
- dispatchable_derived: z.boolean().optional(),
171
- assigned_agent: z.string().optional(),
172
- include_closed: z.boolean().optional(),
195
+ server.tool("issue_list", "List issues for the dispatch's board by default via GET /api/issues. Board-scoped; defaults to the dispatch's board. Pass `board` (a qualified id `<repo>:<slug>`) to list another board instead (unknown board → 404). Nested envelope (DX-935 / DX-937 — hard-cut, no flat params): `filter`the OLD flat filters, now nested (type, parent_id, dispatchable_derived, status_derived[], self_dispatchable_derived, assigned_agent, include_closed, include_deleted, and `q` free-text over id+title+description, the former standalone `q` param now lives at `filter.q`). `fields` opt-in named field-GROUPS (description, ac, comments, retro, dependencies, triage, requires_human, assignment, quality_gates, children, effort); THE DEFAULT RESPONSE (no `fields`) IS MINIMAL only cheap scalar columns (id, type, title, status, parent_id, priority, created_at, updated_at, assigned_agent), zero joins. Point any heavy read (full description, comments[], retro, ac items, dependency edges, triage history, quality-gate rows, children ids) at the matching `fields` entry rather than assuming it's already on the row. `sort` ordered [{column, order}] (id|priority|repo_name|title|type|status_derived|triage_ice_total|created_at|updated_at); absent default order (priority desc, repo_name asc) with an always-appended numeric-id tiebreaker (DX-10 follows DX-9). `limit`/`offset` optional paging (no cap by default). Use issue_get for a single fully-detailed card.", {
196
+ filter: z
197
+ .object({
198
+ q: z.string().optional(),
199
+ type: z.enum(ISSUE_TYPES).optional(),
200
+ parent_id: z.string().nullable().optional(),
201
+ dispatchable_derived: z.boolean().optional(),
202
+ status_derived: z.array(z.string()).optional(),
203
+ self_dispatchable_derived: z.boolean().optional(),
204
+ assigned_agent: z.string().optional(),
205
+ include_closed: z.boolean().optional(),
206
+ include_deleted: z.boolean().optional(),
207
+ })
208
+ .optional()
209
+ .describe("Nested list filters (DX-935) — the hard-cut replacement for the old flat top-level params, including the former standalone `q` (now `filter.q`). Omit entirely for no filtering."),
210
+ fields: z
211
+ .array(z.enum(LIST_FIELD_GROUPS))
212
+ .optional()
213
+ .describe("Opt-in field-GROUPS to add to the minimal default row: description, ac, comments, retro, dependencies, triage, requires_human, assignment, quality_gates, children, effort. Absent/empty = minimal scalars only — no joins."),
214
+ sort: sortField,
173
215
  limit: z.number().int().positive().max(1000).optional(),
174
216
  offset: z.number().int().nonnegative().optional(),
175
217
  ...boardField,
176
218
  }, async (args) => jsonResult(await issueList(client, args)));
177
219
  // ---------------- issue_get ----------------
178
- server.tool("issue_get", 'Fetch a single hydrated issue via GET /api/issues/:id. Board-scoped; defaults to the dispatch\'s board. Issue ids are globally unique, so this resolves from any dispatch regardless of `board`. Returns the full card (every joined child collection: ac [the 2-state facade onto the default "Acceptance Criteria" checklist] + checklists [DX-1290: the full named-checklist model each {name, items:[{label, detail, status: incomplete|failing|passing|cancelled}]}], comments, dependencies, requires_human steps, retro action items + commits, triage history, quality_gates DX-1177: one row per registered quality gate {gate, required, status pending|pass|fail, completed_at, message}; a required PRE gate not yet `pass` pre-empts the work dispatch with the gate reviewer, and `issue_transition complete` refuses while a required POST gate row != pass) plus the ancestor chain walked via parent_id. 404 envelope on unknown id.', {
220
+ server.tool("issue_get", "Fetch a single issue via GET /api/issues/:id. Board-scoped; defaults to the dispatch's board. Issue ids are globally unique, so this resolves from any dispatch regardless of `board`. DEFAULT RESPONSE IS MINIMAL (DX-935 / DX-937) only cheap scalar columns (id, type, title, status, parent_id, priority, created_at, updated_at, assigned_agent); no joined collections. Pass `fields` to opt into named field-GROUPS: description (full description body), ac (acceptance-criteria + checklists model), comments (comments[]), retro (retro good/bad/action_items/commits), dependencies (waiting_on/conflict_on/blocked gate state), triage (triage history + ICE), requires_human (the requires_human gate + steps), assignment (dispatch/assigned_agent/lifecycle timestamps), quality_gates (DX-1177 one row per registered gate {gate, required, status pending|pass|fail, completed_at, message}; a required PRE gate not yet `pass` pre-empts the work dispatch, and `issue_transition complete` refuses while a required POST gate row != pass), children (child id list + rollups), mirrors (external mirror sync state), code_review_items (code-review findings). Point any heavy read at the matching `fields` entry rather than assuming it's already on the row. 404 envelope on unknown id.", {
179
221
  id: z.string().min(1),
222
+ fields: z
223
+ .array(z.enum(GET_FIELD_GROUPS))
224
+ .optional()
225
+ .describe("Opt-in field-GROUPS to add to the minimal default row: description, ac, comments, retro, dependencies, triage, requires_human, assignment, quality_gates, children, mirrors, code_review_items. Absent/empty = minimal scalars only."),
180
226
  ...boardField,
181
227
  }, async (args) => jsonResult(await issueGet(client, args)));
182
228
  // ---------------- issue_create ----------------
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thehammer/danx-dashboard-mcp",
3
- "version": "0.1.31",
3
+ "version": "0.1.32",
4
4
  "description": "Stdio MCP server wrapping danxbot's dashboard /api/issues/* normalized DB-backed HTTP routes for dispatched agents (DX-704 Phase 2).",
5
5
  "license": "MIT",
6
6
  "type": "module",