@thehammer/danx-dashboard-mcp 0.1.83 → 0.1.85
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 +5 -1
- package/dist/index.js +2 -1
- package/dist/listen.js +64 -4
- package/package.json +1 -1
package/dist/handlers.js
CHANGED
|
@@ -392,7 +392,11 @@ export async function issueComment(client, args) {
|
|
|
392
392
|
return client.request({
|
|
393
393
|
method: "POST",
|
|
394
394
|
path: `/${idEnc}/comments`,
|
|
395
|
-
body: {
|
|
395
|
+
body: {
|
|
396
|
+
text,
|
|
397
|
+
...(args.metadata !== undefined ? { metadata: args.metadata } : {}),
|
|
398
|
+
...(args.problem_id !== undefined ? { problem_id: args.problem_id } : {}),
|
|
399
|
+
},
|
|
396
400
|
board,
|
|
397
401
|
});
|
|
398
402
|
}
|
package/dist/index.js
CHANGED
|
@@ -504,12 +504,13 @@ server.tool("issue_triage", "Record a triage confidence score via POST /api/issu
|
|
|
504
504
|
...boardField,
|
|
505
505
|
}, async (args) => jsonResult(await issueTriage(client, args)));
|
|
506
506
|
// ---------------- issue_comment ----------------
|
|
507
|
-
server.tool("issue_comment", "Comment CRUD via /api/issues/:id/comments[/:cid]. action=add → POST {text, metadata?} (server stamps author from bearer + auto-incrementing ordinal); action=edit → PATCH /:cid {text}; action=delete → DELETE /:cid (soft-delete, audit trail preserved — comments are NEVER hard-deleted). Client-supplied author is IGNORED (server-stamped to prevent impersonation). `metadata` (DX-2157, action=add only) is an OPTIONAL opaque JSON object a calling app attaches to the comment — e.g. a generated `{sql, explanation}` packet its own UI renders specially. danxbot stores + returns it verbatim and enforces NO shape on its contents; omit for a plain markdown-only comment (unaffected either way).", {
|
|
507
|
+
server.tool("issue_comment", "Comment CRUD via /api/issues/:id/comments[/:cid]. action=add → POST {text, metadata?, problem_id?} (server stamps author from bearer + auto-incrementing ordinal); action=edit → PATCH /:cid {text}; action=delete → DELETE /:cid (soft-delete, audit trail preserved — comments are NEVER hard-deleted). Client-supplied author is IGNORED (server-stamped to prevent impersonation). `metadata` (DX-2157, action=add only) is an OPTIONAL opaque JSON object a calling app attaches to the comment — e.g. a generated `{sql, explanation}` packet its own UI renders specially. danxbot stores + returns it verbatim and enforces NO shape on its contents; omit for a plain markdown-only comment (unaffected either way). `problem_id` (DX-2906, action=add only) OPTIONALLY threads the comment as a follow-up question under one of this SAME card's problems (from `issue_problem` list/add) WITHOUT answering it — commenting never changes open_problem_count, blocked, or records a decision; use issue_problem's answer route for that. An unknown id, another card's id, or a removed problem's id all 404 naming the problem id — an ANSWERED (but not removed) problem still accepts a follow-up comment, since the thread continues after a decision.", {
|
|
508
508
|
id: z.string().min(1),
|
|
509
509
|
action: z.enum(["add", "edit", "delete"]),
|
|
510
510
|
comment_id: z.number().int().positive().optional(),
|
|
511
511
|
text: z.string().min(1).optional(),
|
|
512
512
|
metadata: z.record(z.unknown()).optional(),
|
|
513
|
+
problem_id: z.number().int().positive().optional().describe("action=add only — thread this comment as a follow-up under a LIVE problem of this same card, without answering it"),
|
|
513
514
|
...boardField,
|
|
514
515
|
}, async (args) => jsonResult(await issueComment(client, args)));
|
|
515
516
|
// ---------------- issue_checklist ----------------
|
package/dist/listen.js
CHANGED
|
@@ -92,8 +92,18 @@ export function invalidEventReason(value) {
|
|
|
92
92
|
if (!isRecord(d))
|
|
93
93
|
return "detail is not an object";
|
|
94
94
|
switch (value.kind) {
|
|
95
|
-
case "comment_added":
|
|
96
|
-
|
|
95
|
+
case "comment_added": {
|
|
96
|
+
if (!isCappedText(d.excerpt))
|
|
97
|
+
return "detail.excerpt is not capped text";
|
|
98
|
+
// DX-2906 — `problem` is OPTIONAL (only present when the comment named
|
|
99
|
+
// one); absent is valid, but a present-and-malformed one is not.
|
|
100
|
+
if (d.problem === undefined)
|
|
101
|
+
return null;
|
|
102
|
+
const problem = d.problem;
|
|
103
|
+
return isRecord(problem) && typeof problem.id === "number" && isCappedText(problem.statement)
|
|
104
|
+
? null
|
|
105
|
+
: "detail.problem is not {id, statement}";
|
|
106
|
+
}
|
|
97
107
|
case "solution_answered": {
|
|
98
108
|
const problem = d.problem;
|
|
99
109
|
if (!isRecord(problem) || typeof problem.id !== "number" || !isCappedText(problem.statement)) {
|
|
@@ -112,6 +122,32 @@ export function invalidEventReason(value) {
|
|
|
112
122
|
}
|
|
113
123
|
return null;
|
|
114
124
|
}
|
|
125
|
+
// DX-2935 — the operator retracted a problem's answer with no new one yet.
|
|
126
|
+
case "problem_unanswered": {
|
|
127
|
+
const problem = d.problem;
|
|
128
|
+
if (!isRecord(problem) || typeof problem.id !== "number" || !isCappedText(problem.statement)) {
|
|
129
|
+
return "detail.problem is not {id, statement}";
|
|
130
|
+
}
|
|
131
|
+
return typeof d.decision_id === "number" ? null : "detail.decision_id is not a number";
|
|
132
|
+
}
|
|
133
|
+
// DX-2935 — the operator retracted a problem's answer AND recorded a new
|
|
134
|
+
// one, in one transaction — same either/or solution/freeform shape
|
|
135
|
+
// `solution_answered` carries, plus which decision was retracted.
|
|
136
|
+
case "problem_answer_changed": {
|
|
137
|
+
const problem = d.problem;
|
|
138
|
+
if (!isRecord(problem) || typeof problem.id !== "number" || !isCappedText(problem.statement)) {
|
|
139
|
+
return "detail.problem is not {id, statement}";
|
|
140
|
+
}
|
|
141
|
+
if (typeof d.retracted_decision_id !== "number")
|
|
142
|
+
return "detail.retracted_decision_id is not a number";
|
|
143
|
+
if (typeof d.decision_id !== "number")
|
|
144
|
+
return "detail.decision_id is not a number";
|
|
145
|
+
if (d.solution === null)
|
|
146
|
+
return isCappedText(d.freeform) ? null : "detail.freeform is not capped text";
|
|
147
|
+
if (!isRecord(d.solution) || typeof d.solution.title !== "string")
|
|
148
|
+
return "detail.solution is not {title, note}";
|
|
149
|
+
return d.solution.note === null || isCappedText(d.solution.note) ? null : "detail.solution.note is not capped text";
|
|
150
|
+
}
|
|
115
151
|
case "blocked":
|
|
116
152
|
return isCappedText(d.reason) ? null : "detail.reason is not capped text";
|
|
117
153
|
default:
|
|
@@ -122,8 +158,16 @@ export function invalidEventReason(value) {
|
|
|
122
158
|
function describe(event) {
|
|
123
159
|
const d = event.detail;
|
|
124
160
|
switch (event.kind) {
|
|
125
|
-
case "comment_added":
|
|
126
|
-
|
|
161
|
+
case "comment_added": {
|
|
162
|
+
// DX-2906 — a comment naming a problem (a follow-up question, without
|
|
163
|
+
// answering it) relays distinctly from a plain comment, so an agent
|
|
164
|
+
// listening to the stream can tell which of its open problems just
|
|
165
|
+
// got a reply.
|
|
166
|
+
const problem = d.problem;
|
|
167
|
+
if (problem === undefined)
|
|
168
|
+
return `${event.actor} commented: ${quoted(d.excerpt)}`;
|
|
169
|
+
return `${event.actor} commented on problem ${quoted(problem.statement)}: ${quoted(d.excerpt)}`;
|
|
170
|
+
}
|
|
127
171
|
case "solution_answered": {
|
|
128
172
|
// DX-2735: every answer answers ONE problem on a card that may carry several,
|
|
129
173
|
// so the line names the problem — otherwise the agent cannot tell which of its
|
|
@@ -142,6 +186,22 @@ function describe(event) {
|
|
|
142
186
|
const problem = d.problem;
|
|
143
187
|
return `${event.actor} opened a problem: ${quoted(problem.statement)}`;
|
|
144
188
|
}
|
|
189
|
+
case "problem_unanswered": {
|
|
190
|
+
// DX-2935 — MUST read as "a previous answer was withdrawn", never as a
|
|
191
|
+
// brand-new problem: a connected session that already acted on the
|
|
192
|
+
// retracted answer has to stop, not treat this as fresh work to start.
|
|
193
|
+
const problem = d.problem;
|
|
194
|
+
return `${event.actor} retracted the answer to ${quoted(problem.statement)}`;
|
|
195
|
+
}
|
|
196
|
+
case "problem_answer_changed": {
|
|
197
|
+
// DX-2935 — ONE line for the whole retract-and-reanswer (never two
|
|
198
|
+
// separate events), naming the statement so a listener with several
|
|
199
|
+
// open questions knows which one just moved.
|
|
200
|
+
const problem = d.problem;
|
|
201
|
+
const solution = d.solution;
|
|
202
|
+
const now = solution === null ? quoted(d.freeform) : `"${solution.title}"`;
|
|
203
|
+
return `${event.actor} changed the answer to ${quoted(problem.statement)}: now ${now}`;
|
|
204
|
+
}
|
|
145
205
|
case "blocked":
|
|
146
206
|
return `${event.actor} blocked the card: ${quoted(d.reason)}`;
|
|
147
207
|
case "unblocked":
|
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.85",
|
|
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",
|