@thehammer/danx-dashboard-mcp 0.1.73 → 0.1.74
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 +72 -0
- package/dist/index.js +45 -1
- package/package.json +1 -1
package/dist/handlers.js
CHANGED
|
@@ -1147,3 +1147,75 @@ export async function planDeleteRecord(client, args) {
|
|
|
1147
1147
|
body: { content_hash: args.content_hash },
|
|
1148
1148
|
});
|
|
1149
1149
|
}
|
|
1150
|
+
// ---------------- failure_category_list / _create / _update (DX-2792) ----------------
|
|
1151
|
+
/**
|
|
1152
|
+
* DX-2792 (Failure evaluation 3/4) — wraps `src/dashboard/failure-categories-routes.ts`,
|
|
1153
|
+
* the DX-2791 (Failure evaluation 2/4) category registry's REST API. INSTALL-
|
|
1154
|
+
* GLOBAL, not board-scoped: `failure_categories` carries no `board_id`
|
|
1155
|
+
* column (every category applies across the whole install), so — unlike
|
|
1156
|
+
* every `/api/issues/*`-backed tool above — these three never send a
|
|
1157
|
+
* `board` query param and the `board` override field is simply absent from
|
|
1158
|
+
* their schemas (mirrors the `plan_*` family's own board-less rationale in
|
|
1159
|
+
* `index.ts`'s `boardField` comment, for the same underlying reason: nothing
|
|
1160
|
+
* server-side would read it).
|
|
1161
|
+
*/
|
|
1162
|
+
const FAILURE_CATEGORIES_BASE_PATH = "/api/failure-categories";
|
|
1163
|
+
/**
|
|
1164
|
+
* List every failure category with its live matched-occurrence count and
|
|
1165
|
+
* last-seen time, via `GET /api/failure-categories`. Returns
|
|
1166
|
+
* `{categories: [{id, name, description, matchers, ignore, ignoreReason,
|
|
1167
|
+
* expectedRate, matchedCount, lastSeenMs, ...}]}`. A fresh install returns
|
|
1168
|
+
* `{categories: []}`.
|
|
1169
|
+
*/
|
|
1170
|
+
export async function failureCategoryList(client) {
|
|
1171
|
+
return client.request({
|
|
1172
|
+
method: "GET",
|
|
1173
|
+
path: "",
|
|
1174
|
+
basePath: FAILURE_CATEGORIES_BASE_PATH,
|
|
1175
|
+
});
|
|
1176
|
+
}
|
|
1177
|
+
/**
|
|
1178
|
+
* Create a new failure category via `POST /api/failure-categories`. At
|
|
1179
|
+
* least one matcher, each with at least one of `sourceKind`/`tool`/
|
|
1180
|
+
* `regexPattern` set, is required (400 otherwise). `ignore: true` REQUIRES a
|
|
1181
|
+
* non-empty `ignoreReason` (400 otherwise). A matcher set that would overlap
|
|
1182
|
+
* an EXISTING category's matchers is refused 400 naming the conflicting
|
|
1183
|
+
* category — expand that category instead of creating a near-duplicate. On
|
|
1184
|
+
* success, the dashboard re-matches every existing uncategorized occurrence
|
|
1185
|
+
* against the new category before responding, so `matchedCount` in the
|
|
1186
|
+
* response already reflects any newly-covered signatures.
|
|
1187
|
+
*/
|
|
1188
|
+
export async function failureCategoryCreate(client, args) {
|
|
1189
|
+
return client.request({
|
|
1190
|
+
method: "POST",
|
|
1191
|
+
path: "",
|
|
1192
|
+
basePath: FAILURE_CATEGORIES_BASE_PATH,
|
|
1193
|
+
body: {
|
|
1194
|
+
name: args.name,
|
|
1195
|
+
description: args.description ?? "",
|
|
1196
|
+
matchers: args.matchers,
|
|
1197
|
+
ignore: args.ignore ?? false,
|
|
1198
|
+
ignoreReason: args.ignoreReason ?? null,
|
|
1199
|
+
expectedRate: args.expectedRate ?? null,
|
|
1200
|
+
},
|
|
1201
|
+
});
|
|
1202
|
+
}
|
|
1203
|
+
/**
|
|
1204
|
+
* Patch an existing failure category via `PATCH /api/failure-categories/:id`
|
|
1205
|
+
* — the tool for BOTH "expand an existing category's matchers" (send the
|
|
1206
|
+
* full replacement `matchers` array) and "mark a category ignored" (send
|
|
1207
|
+
* `ignore: true` + a non-empty `ignoreReason`). At least one field is
|
|
1208
|
+
* required (400 otherwise). Same overlap refusal as create (excluding this
|
|
1209
|
+
* category's own prior matchers). On success, re-matches every
|
|
1210
|
+
* uncategorized occurrence against the updated matcher set before
|
|
1211
|
+
* responding.
|
|
1212
|
+
*/
|
|
1213
|
+
export async function failureCategoryUpdate(client, args) {
|
|
1214
|
+
const { id, ...patch } = args;
|
|
1215
|
+
return client.request({
|
|
1216
|
+
method: "PATCH",
|
|
1217
|
+
path: `/${id}`,
|
|
1218
|
+
basePath: FAILURE_CATEGORIES_BASE_PATH,
|
|
1219
|
+
body: patch,
|
|
1220
|
+
});
|
|
1221
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -49,6 +49,9 @@
|
|
|
49
49
|
* - plan_update_architecture_section PATCH /api/plans/mine/architecture/sections/:sid (DX-2726)
|
|
50
50
|
* - plan_delete_architecture_section DELETE /api/plans/mine/architecture/sections/:sid (DX-2726)
|
|
51
51
|
* - plan_reorder_architecture_section PUT /api/plans/mine/architecture/sections/reorder (DX-2726)
|
|
52
|
+
* - failure_category_list GET /api/failure-categories (DX-2791/DX-2792, board-less)
|
|
53
|
+
* - failure_category_create POST /api/failure-categories (DX-2791/DX-2792, board-less)
|
|
54
|
+
* - failure_category_update PATCH /api/failure-categories/:id (DX-2791/DX-2792, board-less)
|
|
52
55
|
*
|
|
53
56
|
* DX-2683 — THE PLAN TOOLS ARE SESSION-BOUND, and asymmetrically so. Reads
|
|
54
57
|
* may name any plan; WRITES take no plan id at all and act on the plan this
|
|
@@ -87,7 +90,7 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
|
87
90
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
88
91
|
import { z } from "zod";
|
|
89
92
|
import { DashboardHttpClient } from "./http-client.js";
|
|
90
|
-
import { issueAttach, issueChecklist, issueComment, issueCreate, issueDependency, issueEdit, issueGet, issueList, issueProblem, issueQualityGate, issueQualityGateVerdict, issueRetireBranch, issueRetro, issueSolution, issueTransition, issueTriage, briefGetPage, briefList, briefSetPage, planAddArchitectureSection, planAddCard, planAddRecord, planConnect, planCreate, planDeleteArchitectureSection, planDeleteRecord, planGet, PLAN_FIELD_GROUPS, PLAN_STATUSES, ISSUE_BATCH_GET_MAX, LIST_PAGE_MAX_LIMIT, PLAN_GET_CARDS_DEFAULT_LIMIT, planGetArchitectureSection, planGetRecord, planList, planRemoveCard, planRename, planReorderArchitectureSection, planUpdateArchitectureSection, planUpdateRecord, repoKnowledgeGet, repoKnowledgeSet, } from "./handlers.js";
|
|
93
|
+
import { issueAttach, issueChecklist, issueComment, issueCreate, issueDependency, issueEdit, issueGet, issueList, issueProblem, issueQualityGate, issueQualityGateVerdict, issueRetireBranch, issueRetro, issueSolution, issueTransition, issueTriage, briefGetPage, briefList, briefSetPage, failureCategoryCreate, failureCategoryList, failureCategoryUpdate, planAddArchitectureSection, planAddCard, planAddRecord, planConnect, planCreate, planDeleteArchitectureSection, planDeleteRecord, planGet, PLAN_FIELD_GROUPS, PLAN_STATUSES, ISSUE_BATCH_GET_MAX, LIST_PAGE_MAX_LIMIT, PLAN_GET_CARDS_DEFAULT_LIMIT, planGetArchitectureSection, planGetRecord, planList, planRemoveCard, planRename, planReorderArchitectureSection, planUpdateArchitectureSection, planUpdateRecord, repoKnowledgeGet, repoKnowledgeSet, } from "./handlers.js";
|
|
91
94
|
import { PRIORITY_TIER_WORDS } from "./priority.js";
|
|
92
95
|
function readEnvOrDie(name) {
|
|
93
96
|
const v = process.env[name];
|
|
@@ -762,6 +765,47 @@ server.tool("plan_reorder_architecture_section", "Reassign your connected plan's
|
|
|
762
765
|
.min(1)
|
|
763
766
|
.describe("Every live section id of the connected plan, in the desired order — exactly once each."),
|
|
764
767
|
}, async (args) => jsonResult(await planReorderArchitectureSection(client, args)));
|
|
768
|
+
// ---------------- failure_category_list / _create / _update (DX-2792) ----------------
|
|
769
|
+
const matcherField = z
|
|
770
|
+
.object({
|
|
771
|
+
sourceKind: z
|
|
772
|
+
.enum(["tool-error", "hook-refusal", "api-error", "usage-limit", "session-result"])
|
|
773
|
+
.optional()
|
|
774
|
+
.describe("Absent matches any source kind."),
|
|
775
|
+
tool: z.string().min(1).optional().describe("Exact tool name (e.g. \"Bash\"); absent matches any tool, including null."),
|
|
776
|
+
regexPattern: z
|
|
777
|
+
.string()
|
|
778
|
+
.min(1)
|
|
779
|
+
.optional()
|
|
780
|
+
.describe("Regex source tested against the normalized excerpt (paths/UUIDs/timestamps/ports already stripped)."),
|
|
781
|
+
})
|
|
782
|
+
.describe("At least one of sourceKind/tool/regexPattern must be set — an empty matcher is refused.");
|
|
783
|
+
const expectedRateField = z
|
|
784
|
+
.object({
|
|
785
|
+
count: z.number().int().min(0).describe("N — how many failures are expected."),
|
|
786
|
+
overDispatches: z.number().int().positive().describe("X — over how many dispatches."),
|
|
787
|
+
})
|
|
788
|
+
.nullable()
|
|
789
|
+
.optional()
|
|
790
|
+
.describe("Both fields together, or omit/null entirely — never a half-specified rate.");
|
|
791
|
+
server.tool("failure_category_list", "List every failure category via GET /api/failure-categories (DX-2791/DX-2792). Board-less — a category applies across the whole install, not one board. Returns `{categories: [{id, name, description, matchers, ignore, ignoreReason, expectedRate, matchedCount, lastSeenMs, createdAtMs, createdBy, updatedAtMs, updatedBy}]}`, id ascending. A fresh install returns `{categories: []}`. Read this immediately before `failure_category_create`/`failure_category_update` so your overlap/expand decision is against the CURRENT set, not a stale snapshot from earlier in the dispatch.", {}, async () => jsonResult(await failureCategoryList(client)));
|
|
792
|
+
server.tool("failure_category_create", 'Create a new failure category via POST /api/failure-categories (DX-2791/DX-2792). Board-less. `matchers` (at least one) is an OR-across-matchers set — a category matches an occurrence when ANY ONE matcher\'s fields all hold. `ignore: true` REQUIRES a non-empty `ignoreReason` (400 otherwise) — use this for an expected, non-actionable failure rather than leaving it uncategorized. A matcher set overlapping an EXISTING category is refused 400 naming the conflict — call `failure_category_list` first and EXPAND that category (`failure_category_update`) instead of creating a near-duplicate. On success, the dashboard re-matches every existing uncategorized occurrence against the new category before responding.', {
|
|
793
|
+
name: z.string().min(1).describe("The category's name."),
|
|
794
|
+
description: z.string().optional().describe("Optional free-text description. Defaults to empty."),
|
|
795
|
+
matchers: z.array(matcherField).min(1).describe("At least one matcher; a category matches an occurrence when ANY ONE matches (OR across matchers)."),
|
|
796
|
+
ignore: z.boolean().optional().describe("Mark this category as expected/non-actionable noise. Requires ignoreReason. Defaults to false."),
|
|
797
|
+
ignoreReason: z.string().nullable().optional().describe("Required (non-empty) when ignore is true."),
|
|
798
|
+
expectedRate: expectedRateField,
|
|
799
|
+
}, async (args) => jsonResult(await failureCategoryCreate(client, args)));
|
|
800
|
+
server.tool("failure_category_update", "Patch an existing failure category via PATCH /api/failure-categories/:id (DX-2791/DX-2792). Board-less. This is the tool for BOTH actions: expanding an existing category's matchers (send the FULL replacement `matchers` array — it REPLACES, not appends, so include every matcher you want to keep alongside the new one) and marking a category ignored (`ignore: true` + a non-empty `ignoreReason`). At least one field besides `id` is required (400 otherwise). Same overlap refusal as create, excluding this category's own prior matchers. On success, re-matches every uncategorized occurrence against the updated matcher set before responding.", {
|
|
801
|
+
id: z.number().int().positive().describe("The category id to patch — from failure_category_list."),
|
|
802
|
+
name: z.string().min(1).optional(),
|
|
803
|
+
description: z.string().optional(),
|
|
804
|
+
matchers: z.array(matcherField).min(1).optional().describe("REPLACES the full matcher set when sent — never a partial append."),
|
|
805
|
+
ignore: z.boolean().optional(),
|
|
806
|
+
ignoreReason: z.string().nullable().optional(),
|
|
807
|
+
expectedRate: expectedRateField,
|
|
808
|
+
}, async (args) => jsonResult(await failureCategoryUpdate(client, args)));
|
|
765
809
|
// ---------------- main ----------------
|
|
766
810
|
async function main() {
|
|
767
811
|
boot();
|
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.74",
|
|
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",
|