@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 +19 -24
- package/dist/index.js +55 -9
- package/package.json +1 -1
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.
|
|
34
|
-
query.
|
|
35
|
-
|
|
36
|
-
|
|
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.
|
|
39
|
-
query.
|
|
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).
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
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",
|
|
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.
|
|
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",
|