@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 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.73",
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",