@thehammer/danx-dashboard-mcp 0.1.16 → 0.1.17
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/index.js +19 -2
- package/package.json +1 -1
- package/dist/handlers.test.js +0 -51
package/dist/index.js
CHANGED
|
@@ -93,6 +93,13 @@ const EFFORT_VALUES = [
|
|
|
93
93
|
];
|
|
94
94
|
const ISSUE_TYPES = ["Epic", "Bug", "Feature", "Story", "Chore"];
|
|
95
95
|
const NON_EPIC_TYPES = ["Bug", "Feature", "Story", "Chore"];
|
|
96
|
+
// DX-1290 — the uniform 4-state checklist-item status. Terminal = passing|cancelled.
|
|
97
|
+
const CHECKLIST_ITEM_STATUSES = [
|
|
98
|
+
"incomplete",
|
|
99
|
+
"failing",
|
|
100
|
+
"passing",
|
|
101
|
+
"cancelled",
|
|
102
|
+
];
|
|
96
103
|
const TRANSITION_ACTIONS = [
|
|
97
104
|
"ready",
|
|
98
105
|
"pickup",
|
|
@@ -134,7 +141,7 @@ server.tool("issue_list", "List issues for the dispatch's board by default via G
|
|
|
134
141
|
...boardField,
|
|
135
142
|
}, async (args) => jsonResult(await issueList(client, args)));
|
|
136
143
|
// ---------------- issue_get ----------------
|
|
137
|
-
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, 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.", {
|
|
144
|
+
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.", {
|
|
138
145
|
id: z.string().min(1),
|
|
139
146
|
...boardField,
|
|
140
147
|
}, async (args) => jsonResult(await issueGet(client, args)));
|
|
@@ -163,7 +170,7 @@ server.tool("issue_create", "Create a fresh card via POST /api/issues. Board-sco
|
|
|
163
170
|
...boardField,
|
|
164
171
|
}, async (args) => jsonResult(await issueCreate(client, args, config.board)));
|
|
165
172
|
// ---------------- issue_edit ----------------
|
|
166
|
-
server.tool("issue_edit", "Patch prose fields only via PATCH /api/issues/:id/edit. ALLOWED keys: title, description, ac, effort_level, parent_id, list_id. ANY OTHER KEY (lifecycle timestamps, triage state, dependencies, retro, requires_human, blocked/dispatch gates) returns 400 with offending_keys[] and a pointer to the dedicated semantic handler — use issue_transition / issue_triage / issue_comment / issue_dependency / issue_requires_human / issue_retro instead.
|
|
173
|
+
server.tool("issue_edit", "Patch prose fields only via PATCH /api/issues/:id/edit. ALLOWED keys: title, description, ac, checklists, effort_level, parent_id, list_id. ANY OTHER KEY (lifecycle timestamps, triage state, dependencies, retro, requires_human, blocked/dispatch gates) returns 400 with offending_keys[] and a pointer to the dedicated semantic handler — use issue_transition / issue_triage / issue_comment / issue_dependency / issue_requires_human / issue_retro instead. CHECKLISTS (DX-1290): a card carries 0..N named checklists, each item ONE 4-state status `incomplete|failing|passing|cancelled` (terminal = passing|cancelled). `ac` is the 2-state CONVENIENCE onto the default \"Acceptance Criteria\" checklist (checked:true ↔ passing, false ↔ incomplete) — wholesale soft-delete + reinsert of that checklist's items. `checklists` is the GENERIC wholesale write path: it REPLACES every named checklist on the card with full 4-state control (each `{name, items:[{label, detail?, status}]}`) — use it to author named checklists like \"Feature Tests\". Send EITHER `ac` OR `checklists`, NOT both (400). list_id (DX-1192 / DX-1200) PINS the card to a specific board list. **Pass EITHER a board_lists id OR the list's display NAME (case-insensitive, e.g. a queue name like \"⚙️ Fulfillment Queue\") — the server resolves a name to its id.** Its type MUST match the card's CURRENT derived-status list-type (so to route a ToDo card into a `ready`-type queue, ready it first; mismatch / unknown name or id → 400); pass null to clear the pin (back to default-for-type).", {
|
|
167
174
|
id: z.string().min(1),
|
|
168
175
|
title: z.string().min(1).optional(),
|
|
169
176
|
description: z.string().optional(),
|
|
@@ -173,6 +180,16 @@ server.tool("issue_edit", "Patch prose fields only via PATCH /api/issues/:id/edi
|
|
|
173
180
|
checked: z.boolean().optional(),
|
|
174
181
|
}))
|
|
175
182
|
.optional(),
|
|
183
|
+
checklists: z
|
|
184
|
+
.array(z.object({
|
|
185
|
+
name: z.string().min(1),
|
|
186
|
+
items: z.array(z.object({
|
|
187
|
+
label: z.string().min(1),
|
|
188
|
+
detail: z.string().optional(),
|
|
189
|
+
status: z.enum(CHECKLIST_ITEM_STATUSES),
|
|
190
|
+
})),
|
|
191
|
+
}))
|
|
192
|
+
.optional(),
|
|
176
193
|
effort_level: z.enum(EFFORT_VALUES).nullable().optional(),
|
|
177
194
|
parent_id: z.string().nullable().optional(),
|
|
178
195
|
list_id: z.string().min(1).nullable().optional(),
|
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.17",
|
|
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",
|
package/dist/handlers.test.js
DELETED
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from "vitest";
|
|
2
|
-
import { DashboardHttpClient } from "./http-client.js";
|
|
3
|
-
import { issueList, issueTransition } from "./handlers.js";
|
|
4
|
-
/**
|
|
5
|
-
* `issue_list` query-param forwarding. A fake `fetch` captures the built
|
|
6
|
-
* URL so each filter is asserted at the wire, through the real client's
|
|
7
|
-
* URL builder (`?repo=` always stamped, extra query merged after).
|
|
8
|
-
*/
|
|
9
|
-
function clientCapturing() {
|
|
10
|
-
const urls = [];
|
|
11
|
-
const fetchImpl = (async (url) => {
|
|
12
|
-
urls.push(url);
|
|
13
|
-
return new Response(JSON.stringify({ issues: [] }), { status: 200 });
|
|
14
|
-
});
|
|
15
|
-
const client = new DashboardHttpClient({ baseUrl: "http://localhost:5555", repo: "danxbot", token: "t" }, fetchImpl);
|
|
16
|
-
return { client, urls };
|
|
17
|
-
}
|
|
18
|
-
describe("issueList — query forwarding", () => {
|
|
19
|
-
it("forwards q as the server-side search needle", async () => {
|
|
20
|
-
const { client, urls } = clientCapturing();
|
|
21
|
-
await issueList(client, { q: "retire" });
|
|
22
|
-
expect(urls[0]).toContain("q=retire");
|
|
23
|
-
});
|
|
24
|
-
it("omits q when not provided", async () => {
|
|
25
|
-
const { client, urls } = clientCapturing();
|
|
26
|
-
await issueList(client, { include_closed: true });
|
|
27
|
-
expect(urls[0]).not.toContain("q=");
|
|
28
|
-
expect(urls[0]).toContain("include_closed=true");
|
|
29
|
-
});
|
|
30
|
-
});
|
|
31
|
-
describe("issueTransition — body forwarding", () => {
|
|
32
|
-
function clientCapturingBody() {
|
|
33
|
-
const bodies = [];
|
|
34
|
-
const fetchImpl = (async (_url, init) => {
|
|
35
|
-
bodies.push(JSON.parse(String(init?.body ?? "{}")));
|
|
36
|
-
return new Response(JSON.stringify({ issue: {} }), { status: 200 });
|
|
37
|
-
});
|
|
38
|
-
const client = new DashboardHttpClient({ baseUrl: "http://localhost:5555", repo: "danxbot", token: "t" }, fetchImpl);
|
|
39
|
-
return { client, bodies };
|
|
40
|
-
}
|
|
41
|
-
it("forwards manual: true on pickup (DX-946 operator self-pickup)", async () => {
|
|
42
|
-
const { client, bodies } = clientCapturingBody();
|
|
43
|
-
await issueTransition(client, { id: "DX-1", action: "pickup", manual: true });
|
|
44
|
-
expect(bodies[0]).toEqual({ action: "pickup", manual: true });
|
|
45
|
-
});
|
|
46
|
-
it("omits manual when not provided", async () => {
|
|
47
|
-
const { client, bodies } = clientCapturingBody();
|
|
48
|
-
await issueTransition(client, { id: "DX-1", action: "pickup" });
|
|
49
|
-
expect(bodies[0]).toEqual({ action: "pickup" });
|
|
50
|
-
});
|
|
51
|
-
});
|