@thehammer/danx-dashboard-mcp 0.1.120 → 0.1.123
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 +60 -6
- package/dist/index.js +84 -11
- package/package.json +1 -1
package/dist/handlers.js
CHANGED
|
@@ -679,12 +679,13 @@ export async function issueProblem(client, args) {
|
|
|
679
679
|
}
|
|
680
680
|
}
|
|
681
681
|
/**
|
|
682
|
-
* One problem's candidate solutions
|
|
683
|
-
* `/api/issues/:id/problems/:pid/solutions[/:sid]`
|
|
684
|
-
* like `issue_checklist`. A missing required arg for the
|
|
685
|
-
* this boundary (no round-trip); the server's
|
|
686
|
-
* (`stale_solution` with the current row, a
|
|
687
|
-
* problem, editing a chosen option, a
|
|
682
|
+
* One problem's candidate solutions, AND (DX-3310) one solution's individual
|
|
683
|
+
* procedure steps, via `/api/issues/:id/problems/:pid/solutions[/:sid][/steps[/:stepId]]`
|
|
684
|
+
* — action-dispatched like `issue_checklist`. A missing required arg for the
|
|
685
|
+
* chosen action throws at this boundary (no round-trip); the server's
|
|
686
|
+
* refusal envelopes (`stale_solution`/`stale_step` with the current row, a
|
|
687
|
+
* second recommendation on the same problem, editing a chosen option, a
|
|
688
|
+
* solution/step id from the wrong scope → 404, nesting past depth 3 → 400)
|
|
688
689
|
* pass through verbatim.
|
|
689
690
|
*
|
|
690
691
|
* There is deliberately NO answer action, on this tool or any other. Answering
|
|
@@ -708,11 +709,28 @@ export async function issueSolution(client, args) {
|
|
|
708
709
|
case "add": {
|
|
709
710
|
// DX-2735: the POST carries the CHECKED title, not a re-read of args.title.
|
|
710
711
|
const title = need.string(args.title, "title");
|
|
712
|
+
// DX-3310 — omitted `steps` on add means "no steps", not "leave
|
|
713
|
+
// untouched" (there is nothing yet to leave untouched on a brand new
|
|
714
|
+
// solution) — sent through only when the caller actually supplied it,
|
|
715
|
+
// so an existing add-only caller that never heard of `steps` keeps
|
|
716
|
+
// creating exactly what it always created.
|
|
717
|
+
if (args.steps !== undefined)
|
|
718
|
+
content.steps = args.steps;
|
|
711
719
|
return client.request({ method: "POST", path: base, body: { ...content, title }, board });
|
|
712
720
|
}
|
|
713
721
|
case "edit": {
|
|
714
722
|
const solutionId = need.id(args.solution_id, "solution_id");
|
|
715
723
|
const baseHash = need.string(args.base_hash, "base_hash");
|
|
724
|
+
// DX-3310 — omitted keeps the stored steps untouched; an explicit
|
|
725
|
+
// (including empty) array replaces them via the server's diff.
|
|
726
|
+
if (args.steps !== undefined) {
|
|
727
|
+
content.steps = args.steps;
|
|
728
|
+
// B2 (code-review fix) — REQUIRED alongside `steps`, checked here
|
|
729
|
+
// (not only by the server) so a caller gets the teaching error at
|
|
730
|
+
// this boundary rather than a round trip for something this client
|
|
731
|
+
// already knows is missing.
|
|
732
|
+
content.steps_base_hash = need.string(args.steps_base_hash, "steps_base_hash");
|
|
733
|
+
}
|
|
716
734
|
return client.request({
|
|
717
735
|
method: "PATCH",
|
|
718
736
|
path: `${base}/${solutionId}`,
|
|
@@ -730,6 +748,42 @@ export async function issueSolution(client, args) {
|
|
|
730
748
|
board,
|
|
731
749
|
});
|
|
732
750
|
}
|
|
751
|
+
// DX-3310 (AC 35250) — granular single-step CUD, so changing one step
|
|
752
|
+
// never requires resending the whole `steps` tree.
|
|
753
|
+
case "add_step": {
|
|
754
|
+
const solutionId = need.id(args.solution_id, "solution_id");
|
|
755
|
+
const title = need.string(args.title, "title");
|
|
756
|
+
const body = { title };
|
|
757
|
+
if (args.description !== undefined)
|
|
758
|
+
body.description = args.description;
|
|
759
|
+
if (args.parent_step_id !== undefined)
|
|
760
|
+
body.parent_step_id = args.parent_step_id;
|
|
761
|
+
if (args.position !== undefined)
|
|
762
|
+
body.position = args.position;
|
|
763
|
+
return client.request({ method: "POST", path: `${base}/${solutionId}/steps`, body, board });
|
|
764
|
+
}
|
|
765
|
+
case "edit_step": {
|
|
766
|
+
const solutionId = need.id(args.solution_id, "solution_id");
|
|
767
|
+
const stepId = need.id(args.step_id, "step_id");
|
|
768
|
+
const baseHash = need.string(args.base_hash, "base_hash");
|
|
769
|
+
const body = { base_hash: baseHash };
|
|
770
|
+
if (args.title !== undefined)
|
|
771
|
+
body.title = args.title;
|
|
772
|
+
if (args.description !== undefined)
|
|
773
|
+
body.description = args.description;
|
|
774
|
+
return client.request({ method: "PATCH", path: `${base}/${solutionId}/steps/${stepId}`, body, board });
|
|
775
|
+
}
|
|
776
|
+
case "remove_step": {
|
|
777
|
+
const solutionId = need.id(args.solution_id, "solution_id");
|
|
778
|
+
const stepId = need.id(args.step_id, "step_id");
|
|
779
|
+
const baseHash = need.string(args.base_hash, "base_hash");
|
|
780
|
+
return client.request({
|
|
781
|
+
method: "DELETE",
|
|
782
|
+
path: `${base}/${solutionId}/steps/${stepId}`,
|
|
783
|
+
body: { base_hash: baseHash },
|
|
784
|
+
board,
|
|
785
|
+
});
|
|
786
|
+
}
|
|
733
787
|
}
|
|
734
788
|
}
|
|
735
789
|
/**
|
package/dist/index.js
CHANGED
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
* - issue_comment POST/PATCH/DELETE /api/issues/:id/comments[/:cid]
|
|
21
21
|
* - issue_checklist POST/PATCH/DELETE /api/issues/:id/checklists[/:cid[/items[/:iid]]]
|
|
22
22
|
* - issue_problem GET/POST/PATCH/DELETE /api/issues/:id/problems[/:pid] (DX-2735)
|
|
23
|
-
* - issue_solution POST/PATCH/DELETE /api/issues/:id/problems/:pid/solutions[/:sid] (DX-2735)
|
|
23
|
+
* - issue_solution POST/PATCH/DELETE /api/issues/:id/problems/:pid/solutions[/:sid][/steps[/:stepId]] (DX-2735, steps DX-3310)
|
|
24
24
|
* - issue_dependency POST/DELETE /api/issues/:id/dependencies[/:did]
|
|
25
25
|
* - issue_retire_branch POST /api/issues/:id/card-branch-retire (DX-2845)
|
|
26
26
|
* - issue_quality_gate POST /api/issues/:id/quality-gates/:gate
|
|
@@ -626,6 +626,28 @@ const SOLUTION_FIELDS = {
|
|
|
626
626
|
con: z.string().optional(),
|
|
627
627
|
recommended: z.boolean().optional(),
|
|
628
628
|
};
|
|
629
|
+
const STEP_INPUT = z.lazy(() => z
|
|
630
|
+
.object({
|
|
631
|
+
id: z
|
|
632
|
+
.number()
|
|
633
|
+
.int()
|
|
634
|
+
.positive()
|
|
635
|
+
.optional()
|
|
636
|
+
.describe("present -> this node IS an existing step (matched on this id, 400 if it does not resolve to a live " +
|
|
637
|
+
"child of this exact parent scope — a supplied id is NEVER claimed by the positional/title fallback " +
|
|
638
|
+
"below); absent -> matched against unclaimed siblings by TITLE (first unclaimed match, in order), else " +
|
|
639
|
+
"created new. An id-less MIDDLE insert recreates every later id-less sibling's row unless you send ids " +
|
|
640
|
+
"for the ones you want preserved — send ids whenever an existing step's identity matters (checked " +
|
|
641
|
+
"progress, a later single-step edit)."),
|
|
642
|
+
title: z
|
|
643
|
+
.string()
|
|
644
|
+
.min(1)
|
|
645
|
+
.describe("one imperative line, the thing to do — labels (\"1.\", \"2a\") are DERIVED on read and must never appear " +
|
|
646
|
+
"in the title itself."),
|
|
647
|
+
description: z.string().nullable().optional().describe("markdown detail, or null/omitted for none"),
|
|
648
|
+
steps: z.array(STEP_INPUT).optional().describe("this step's own children, nested — refused past 3 levels total"),
|
|
649
|
+
})
|
|
650
|
+
.strict());
|
|
629
651
|
strictTool("issue_problem", "A card's PROBLEMS via /api/issues/:id/problems[/:pid]: one statement the operator must resolve (a question, or a flaw in the plan) OR an action only a person can carry out, each with its own solutions/answers — one problem per question. OPEN = not yet answered; the card needs a human exactly while open_problem_count > 0 — there is no separate flag to set, adding a problem IS putting the card in front of a human. list → live problems in order, each {id, statement, context, type, summary, content_hash, open, solutions[], decisions[]}; add {statement, context?, type?, summary?, solutions?} → problem_id + solution_ids in one transaction (zero solutions is valid: the operator answers free-form) plus `problems_reminder: {open_problem_count, instruction}` naming each open problem's solution count; edit :pid {base_hash, statement, context?, summary?}; remove :pid {base_hash} — always allowed, even as the card's last open problem (removing it just means the card no longer needs a human). A stale base_hash → 409 `stale_problem` with currentHash + currentProblem: merge, then retry. No answer action — the operator answers in the dashboard. `statement` is capped at 200 characters (a 400 names the actual length otherwise): write it as ONE plain sentence (a question for a question, the deed itself for an action), and put any investigation detail in `context` (markdown, no cap) instead of running it on. Good: statement \"Which cache should we use?\", context \"Redis fits the read-heavy path; see benchmark in #123. Memcached is simpler ops but no persistence.\" Bad: statement \"We looked at Redis vs Memcached, ran benchmarks showing Redis 3x faster on reads, but Memcached has simpler ops and we're not sure persistence matters here since the cache is fully rebuildable from Postgres...\" (too long, refused — move it to context). Every problem is also a `type`, with a `summary` distinct from `context`: read BOTH fields' own descriptions below before your first `add` — together they teach which type this is, and the three-field split (`statement` / `summary` / `context`) an action actually needs.", {
|
|
630
652
|
id: z.string().min(1),
|
|
631
653
|
action: z.enum(["list", "add", "edit", "remove"]),
|
|
@@ -659,9 +681,9 @@ strictTool("issue_problem", "A card's PROBLEMS via /api/issues/:id/problems[/:pi
|
|
|
659
681
|
"WHY THE ACTION IS NEEDED AND WHY YOU CANNOT DO IT YOURSELF — that sentence is what tells the operator this " +
|
|
660
682
|
"is not you being lazy (an add with no summary is refused 400); context is whatever additional detail the " +
|
|
661
683
|
"person needs to carry it out; solutions[] are the possible ROUTES a person could take (e.g. \"rotate by " +
|
|
662
|
-
"hand in the console\" vs \"run the provisioning script\" vs \"ask the vendor\"),
|
|
663
|
-
"
|
|
664
|
-
"every time."),
|
|
684
|
+
"hand in the console\" vs \"run the provisioning script\" vs \"ask the vendor\"), and EACH ONE MUST CARRY " +
|
|
685
|
+
"AT LEAST ONE STEP (refused 400 otherwise) — an action with no procedure is not yet actionable. Omitted " +
|
|
686
|
+
"defaults to \"question\" — decide deliberately every time."),
|
|
665
687
|
summary: z
|
|
666
688
|
.string()
|
|
667
689
|
.nullable()
|
|
@@ -673,20 +695,71 @@ strictTool("issue_problem", "A card's PROBLEMS via /api/issues/:id/problems[/:pi
|
|
|
673
695
|
"when given (omit/null on a question means none). edit: omit to keep the stored summary, null to clear it " +
|
|
674
696
|
"(refused if the problem is a live action)."),
|
|
675
697
|
solutions: z
|
|
676
|
-
.array(z.object({ title: z.string().min(1), ...SOLUTION_FIELDS }).strict())
|
|
698
|
+
.array(z.object({ title: z.string().min(1), ...SOLUTION_FIELDS, steps: z.array(STEP_INPUT).optional() }).strict())
|
|
677
699
|
.optional()
|
|
678
|
-
.describe("add only; fields as issue_solution add"
|
|
700
|
+
.describe("add only; fields as issue_solution add, INCLUDING steps (S6/AC 35332) — an ACTION problem's inline " +
|
|
701
|
+
"solutions must each carry at least one step (refused 400 otherwise); a QUESTION's may carry any number, " +
|
|
702
|
+
"including none."),
|
|
679
703
|
...boardField,
|
|
680
704
|
}, async (args) => jsonResult(await issueProblem(client, args)));
|
|
681
705
|
// ---------------- issue_solution ----------------
|
|
682
|
-
strictTool("issue_solution", "One problem's options via /api/issues/:id/problems/:pid/solutions[/:sid]
|
|
706
|
+
strictTool("issue_solution", "One problem's options via /api/issues/:id/problems/:pid/solutions[/:sid], AND (DX-3310) one solution's individual " +
|
|
707
|
+
"procedure steps via .../solutions/:sid/steps[/:stepId]; `problem_id` is REQUIRED (from issue_problem list/add; " +
|
|
708
|
+
"another problem's solution id → 404). add {title, body?, pro?, con?, recommended?, steps?}: title names the " +
|
|
709
|
+
"option/route, body is its markdown detail, pro/con the case for and against, `steps` is its WHOLE procedure " +
|
|
710
|
+
"authored in one call — an ordered array of {title, description?, steps?}, nestable to 3 levels (a 4th is refused " +
|
|
711
|
+
"400 naming the offending step and the limit). AC 35332 — under an ACTION problem, `steps` must be non-empty " +
|
|
712
|
+
"(refused 400 otherwise); under a QUESTION it may be omitted or empty. Labels (\"1\", \"2a\", \"2a.i\") are " +
|
|
713
|
+
"ALWAYS DERIVED on read from position — never put one in a title yourself. edit :sid {base_hash, ...only the " +
|
|
714
|
+
"changed fields, steps?, steps_base_hash?}: `steps` omitted leaves the stored procedure untouched, an explicit " +
|
|
715
|
+
"array (including [], refused if it would leave an action's solution with zero steps) DIFFS against it — send " +
|
|
716
|
+
"back an existing step's `id` (read from the card) to keep it (even while retitling/reordering it) and omit " +
|
|
717
|
+
"`id` for a genuinely new step; any existing step you don't include gets removed, and an id-less MIDDLE insert " +
|
|
718
|
+
"recreates every later id-less sibling's row unless you send ids for the ones whose identity matters. " +
|
|
719
|
+
"`steps_base_hash` is REQUIRED whenever `steps` is sent — the solution's current `steps_hash` (read it off the " +
|
|
720
|
+
"card first); a stale value → 409 `stale_steps` carrying the current tree, so a step added/removed since your " +
|
|
721
|
+
"read is never silently dropped. NEVER resend the whole tree just to fix one word — see add_step/edit_step/" +
|
|
722
|
+
"remove_step below. remove :sid {base_hash}. A stale base_hash → 409 `stale_solution` with currentHash + " +
|
|
723
|
+
"currentSolution: merge, then retry. At most ONE live recommended per problem: a second → 409 naming " +
|
|
724
|
+
"`recommended_solution_id`. A CHOSEN option's words are frozen (409 — add a new one instead), and so is its " +
|
|
725
|
+
"WHOLE PROCEDURE — every steps route below also refuses once a decision has chosen this solution.\n\n" +
|
|
726
|
+
"Granular single-step actions — change ONE step without resending the tree: add_step {solution_id, title, " +
|
|
727
|
+
"description?, parent_step_id?, position?} — title is ONE imperative line, never a label like \"1.\" or \"2a\" " +
|
|
728
|
+
"(derived on read); parent_step_id omitted/null = top-level; position is 1-indexed among the parent's current " +
|
|
729
|
+
"live children, omitted = append, and a position beyond the current sibling count is refused (400), never " +
|
|
730
|
+
"silently clamped to append; nesting past depth 3 → 400. edit_step {solution_id, step_id, base_hash, title?, " +
|
|
731
|
+
"description?} — never moves a step (no parent_step_id/position here). remove_step {solution_id, step_id, " +
|
|
732
|
+
"base_hash} — soft-deletes it AND its own live children; refused (409) if this solution belongs to an ACTION " +
|
|
733
|
+
"problem and removing it would leave zero live steps. A stale base_hash on a step → 409 `stale_step` with " +
|
|
734
|
+
"currentHash + currentStep (carrying its real derived `label`).", {
|
|
683
735
|
id: z.string().min(1),
|
|
684
|
-
action: z.enum(["add", "edit", "remove"]),
|
|
736
|
+
action: z.enum(["add", "edit", "remove", "add_step", "edit_step", "remove_step"]),
|
|
685
737
|
problem_id: z.number().int().positive(),
|
|
686
|
-
solution_id: z.number().int().positive().optional().describe("edit/remove"),
|
|
687
|
-
base_hash: z.string().min(1).optional().describe("content_hash last read; edit/remove"),
|
|
688
|
-
title: z.string().min(1).optional().describe("add"),
|
|
738
|
+
solution_id: z.number().int().positive().optional().describe("edit/remove/add_step/edit_step/remove_step"),
|
|
739
|
+
base_hash: z.string().min(1).optional().describe("content_hash last read; edit/remove/edit_step/remove_step"),
|
|
740
|
+
title: z.string().min(1).optional().describe("add; also add_step/edit_step"),
|
|
689
741
|
...SOLUTION_FIELDS,
|
|
742
|
+
steps: z.array(STEP_INPUT).optional().describe("add/edit only — see this tool's own description"),
|
|
743
|
+
steps_base_hash: z
|
|
744
|
+
.string()
|
|
745
|
+
.min(1)
|
|
746
|
+
.optional()
|
|
747
|
+
.describe("edit only, REQUIRED whenever steps is sent — the solution's current steps_hash; a stale value is refused 409 stale_steps"),
|
|
748
|
+
description: z.string().nullable().optional().describe("add_step/edit_step; markdown detail, or null/omitted for none"),
|
|
749
|
+
step_id: z.number().int().positive().optional().describe("edit_step/remove_step"),
|
|
750
|
+
parent_step_id: z
|
|
751
|
+
.number()
|
|
752
|
+
.int()
|
|
753
|
+
.positive()
|
|
754
|
+
.nullable()
|
|
755
|
+
.optional()
|
|
756
|
+
.describe("add_step only; omitted/null = top-level step"),
|
|
757
|
+
position: z
|
|
758
|
+
.number()
|
|
759
|
+
.int()
|
|
760
|
+
.positive()
|
|
761
|
+
.optional()
|
|
762
|
+
.describe("add_step only; 1-indexed among the parent's current live children, omitted = append"),
|
|
690
763
|
...boardField,
|
|
691
764
|
}, async (args) => jsonResult(await issueSolution(client, args)));
|
|
692
765
|
// ---------------- issue_dependency ----------------
|
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.123",
|
|
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",
|