@thehammer/danx-dashboard-mcp 0.1.12 → 0.1.13
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 +2 -0
- package/dist/handlers.test.js +51 -0
- package/dist/index.js +4 -2
- package/package.json +1 -1
package/dist/handlers.js
CHANGED
|
@@ -61,6 +61,8 @@ export async function issueCreate(client, args, defaultBoard) {
|
|
|
61
61
|
body.ac = args.ac;
|
|
62
62
|
if (args.effort_level !== undefined)
|
|
63
63
|
body.effort_level = args.effort_level;
|
|
64
|
+
if (args.list_id !== undefined)
|
|
65
|
+
body.list_id = args.list_id;
|
|
64
66
|
if (args.phase_children !== undefined)
|
|
65
67
|
body.phase_children = args.phase_children;
|
|
66
68
|
return client.request({ method: "POST", path: "", body, board });
|
|
@@ -0,0 +1,51 @@
|
|
|
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
|
+
});
|
package/dist/index.js
CHANGED
|
@@ -139,13 +139,14 @@ server.tool("issue_get", "Fetch a single hydrated issue via GET /api/issues/:id.
|
|
|
139
139
|
...boardField,
|
|
140
140
|
}, async (args) => jsonResult(await issueGet(client, args)));
|
|
141
141
|
// ---------------- issue_create ----------------
|
|
142
|
-
server.tool("issue_create", "Create a fresh card via POST /api/issues. Board-scoped; defaults to the dispatch's board. Pass `board` (a qualified id `<repo>:<slug>`) to create the card on another board (forwarded into body.board + ?board=; unknown board → 404). INVARIANT: type=Epic REQUIRES non-empty phase_children[] (epic-with-phases atomicity per DX-575) and the route atomically inserts the epic + every phase in ONE transaction. Non-Epic types REFUSE phase_children[] with 400. Status defaults to Review (no lifecycle timestamps stamped on create). parent_id optional. ac items take {title}; phase children inherit the new epic's id as parent_id.", {
|
|
142
|
+
server.tool("issue_create", "Create a fresh card via POST /api/issues. Board-scoped; defaults to the dispatch's board. Pass `board` (a qualified id `<repo>:<slug>`) to create the card on another board (forwarded into body.board + ?board=; unknown board → 404). INVARIANT: type=Epic REQUIRES non-empty phase_children[] (epic-with-phases atomicity per DX-575) and the route atomically inserts the epic + every phase in ONE transaction. Non-Epic types REFUSE phase_children[] with 400. Status defaults to Review (no lifecycle timestamps stamped on create). parent_id optional. ac items take {title}; phase children inherit the new epic's id as parent_id. Optional list_id (DX-1192) PINS the card to a specific board list (a board_lists id) instead of the default-for-type — its type MUST match the card's derived-status list-type (a fresh card is Review → only a review-type list is valid at create; mismatch/unknown id → 400). Omit for the default.", {
|
|
143
143
|
type: z.enum(ISSUE_TYPES),
|
|
144
144
|
title: z.string().min(1),
|
|
145
145
|
description: z.string(),
|
|
146
146
|
parent_id: z.string().nullable().optional(),
|
|
147
147
|
ac: z.array(z.object({ title: z.string().min(1) })).optional(),
|
|
148
148
|
effort_level: z.enum(EFFORT_VALUES).nullable().optional(),
|
|
149
|
+
list_id: z.string().min(1).nullable().optional(),
|
|
149
150
|
phase_children: z
|
|
150
151
|
.array(z.object({
|
|
151
152
|
type: z.enum(NON_EPIC_TYPES),
|
|
@@ -158,7 +159,7 @@ server.tool("issue_create", "Create a fresh card via POST /api/issues. Board-sco
|
|
|
158
159
|
...boardField,
|
|
159
160
|
}, async (args) => jsonResult(await issueCreate(client, args, config.board)));
|
|
160
161
|
// ---------------- issue_edit ----------------
|
|
161
|
-
server.tool("issue_edit", "Patch prose fields only via PATCH /api/issues/:id/edit. ALLOWED keys: title, description, ac, effort_level, parent_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. AC replacement is wholesale soft-delete + reinsert with fresh ordinals; check_item_id linkage survives via title match.", {
|
|
162
|
+
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. AC replacement is wholesale soft-delete + reinsert with fresh ordinals; check_item_id linkage survives via title match. list_id (DX-1192) PINS the card to a specific board list (a board_lists id) — its type MUST match the card's CURRENT derived-status list-type (mismatch/unknown id → 400); pass null to clear the pin (back to default-for-type).", {
|
|
162
163
|
id: z.string().min(1),
|
|
163
164
|
title: z.string().min(1).optional(),
|
|
164
165
|
description: z.string().optional(),
|
|
@@ -170,6 +171,7 @@ server.tool("issue_edit", "Patch prose fields only via PATCH /api/issues/:id/edi
|
|
|
170
171
|
.optional(),
|
|
171
172
|
effort_level: z.enum(EFFORT_VALUES).nullable().optional(),
|
|
172
173
|
parent_id: z.string().nullable().optional(),
|
|
174
|
+
list_id: z.string().min(1).nullable().optional(),
|
|
173
175
|
...boardField,
|
|
174
176
|
}, async (args) => jsonResult(await issueEdit(client, args)));
|
|
175
177
|
// ---------------- issue_transition ----------------
|
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.13",
|
|
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",
|