@thehammer/danx-dashboard-mcp 0.1.142 → 0.1.144
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 +61 -0
- package/dist/index.js +50 -55
- package/package.json +1 -1
package/dist/handlers.js
CHANGED
|
@@ -336,6 +336,39 @@ function argCheckers(tool, mode) {
|
|
|
336
336
|
},
|
|
337
337
|
};
|
|
338
338
|
}
|
|
339
|
+
/**
|
|
340
|
+
* Every action-dispatched tool's args carry these three regardless of
|
|
341
|
+
* `action` — never action-specific, so every per-action allow-list passed to
|
|
342
|
+
* `refuseInapplicableFields` is unioned with this rather than repeating it.
|
|
343
|
+
*/
|
|
344
|
+
const STRUCTURAL_ACTION_FIELDS = ["id", "action", "board"];
|
|
345
|
+
/**
|
|
346
|
+
* DX-3310 (code-review nit) — a field the caller sent that means nothing for
|
|
347
|
+
* the chosen `action` used to be silently ignored: `issue_solution`'s `add`
|
|
348
|
+
* built its `content` object from `title`/`body`/`pro`/`con`/`recommended`
|
|
349
|
+
* regardless of action, then simply never read it on `remove`/`add_step`/etc,
|
|
350
|
+
* and `issue_problem`'s `remove` never looked at `statement`/`context`/
|
|
351
|
+
* `solutions` at all. Either way the caller got a 200 with the field quietly
|
|
352
|
+
* discarded — no different from a typo. Refused loud instead, mirroring
|
|
353
|
+
* `argCheckers`' own wording: every ACTUALLY-SUPPLIED (`!== undefined`) key
|
|
354
|
+
* outside the action's own allow-list (plus `STRUCTURAL_ACTION_FIELDS`,
|
|
355
|
+
* always allowed) is named in one error, so a caller fixes every offending
|
|
356
|
+
* field in one round trip rather than one at a time.
|
|
357
|
+
*/
|
|
358
|
+
// A9 (code-review fix) — takes `object` rather than `Record<string, unknown>`
|
|
359
|
+
// so every caller (each with its own specific `*Args` interface, no index
|
|
360
|
+
// signature) passes `args` directly, with no `as unknown as Record<...>`
|
|
361
|
+
// bridge cast at the call site. `Object.entries` reads the key/value pairs
|
|
362
|
+
// without needing an index signature on the input type either.
|
|
363
|
+
function refuseInapplicableFields(tool, mode, args, allowed) {
|
|
364
|
+
const offending = Object.entries(args)
|
|
365
|
+
.filter(([key, value]) => value !== undefined && !STRUCTURAL_ACTION_FIELDS.includes(key) && !allowed.has(key))
|
|
366
|
+
.map(([key]) => key)
|
|
367
|
+
.sort();
|
|
368
|
+
if (offending.length > 0) {
|
|
369
|
+
throw new Error(`${tool} ${mode} refuses fields that do not apply to this action: ${offending.join(", ")}`);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
339
372
|
export async function issueComment(client, args) {
|
|
340
373
|
const idEnc = encodeURIComponent(args.id);
|
|
341
374
|
const board = args.board;
|
|
@@ -537,10 +570,25 @@ export async function issueChecklist(client, args) {
|
|
|
537
570
|
*
|
|
538
571
|
* No answer action, for the reason `issueSolution` gives.
|
|
539
572
|
*/
|
|
573
|
+
/**
|
|
574
|
+
* DX-3310 (nit) — per-action allow-list for `refuseInapplicableFields`.
|
|
575
|
+
* `id`/`action`/`board` are structural and never listed (every action takes
|
|
576
|
+
* them). `type` is listed under `edit` even though it can never actually be
|
|
577
|
+
* SENT there (see the dedicated throw below) — that keeps the generic
|
|
578
|
+
* refusal from firing first with a less specific message than the one that
|
|
579
|
+
* already names the real reason.
|
|
580
|
+
*/
|
|
581
|
+
const PROBLEM_ACTION_FIELDS = {
|
|
582
|
+
list: [],
|
|
583
|
+
add: ["statement", "context", "type", "summary", "solutions"],
|
|
584
|
+
edit: ["problem_id", "base_hash", "statement", "context", "summary", "type"],
|
|
585
|
+
remove: ["problem_id", "base_hash"],
|
|
586
|
+
};
|
|
540
587
|
export async function issueProblem(client, args) {
|
|
541
588
|
const idEnc = encodeURIComponent(args.id);
|
|
542
589
|
const board = args.board;
|
|
543
590
|
const need = argCheckers("issue_problem", `action=${args.action}`);
|
|
591
|
+
refuseInapplicableFields("issue_problem", `action=${args.action}`, args, new Set(PROBLEM_ACTION_FIELDS[args.action]));
|
|
544
592
|
switch (args.action) {
|
|
545
593
|
case "list":
|
|
546
594
|
return client.request({ method: "GET", path: `/${idEnc}/problems`, board });
|
|
@@ -621,11 +669,24 @@ export async function issueProblem(client, args) {
|
|
|
621
669
|
* question could release the very stop it set to wait for a human. The operator
|
|
622
670
|
* answers in the dashboard.
|
|
623
671
|
*/
|
|
672
|
+
const SOLUTION_ACTION_FIELDS = {
|
|
673
|
+
add: ["title", "body", "pro", "con", "recommended", "steps"],
|
|
674
|
+
edit: ["solution_id", "base_hash", "title", "body", "pro", "con", "recommended", "steps", "steps_base_hash"],
|
|
675
|
+
remove: ["solution_id", "base_hash"],
|
|
676
|
+
add_step: ["solution_id", "title", "description", "parent_step_id", "position"],
|
|
677
|
+
edit_step: ["solution_id", "step_id", "base_hash", "title", "description"],
|
|
678
|
+
remove_step: ["solution_id", "step_id", "base_hash"],
|
|
679
|
+
};
|
|
624
680
|
export async function issueSolution(client, args) {
|
|
625
681
|
const need = argCheckers("issue_solution", `action=${args.action}`);
|
|
626
682
|
// DX-2735: checked at runtime too, not only by the schema — a caller that skips
|
|
627
683
|
// the MCP boundary must never build `/problems/undefined/solutions`.
|
|
628
684
|
const problemId = need.id(args.problem_id, "problem_id");
|
|
685
|
+
// DX-3310 (nit) — `problem_id` is unioned in here rather than listed in
|
|
686
|
+
// every `SOLUTION_ACTION_FIELDS` entry: unlike `issue_problem` (where it
|
|
687
|
+
// only applies to edit/remove), `issue_solution` requires it for every
|
|
688
|
+
// action (checked above), so it is structural FOR THIS TOOL specifically.
|
|
689
|
+
refuseInapplicableFields("issue_solution", `action=${args.action}`, args, new Set(["problem_id", ...SOLUTION_ACTION_FIELDS[args.action]]));
|
|
629
690
|
const base = `/${encodeURIComponent(args.id)}/problems/${problemId}/solutions`;
|
|
630
691
|
const board = args.board;
|
|
631
692
|
const content = {};
|
package/dist/index.js
CHANGED
|
@@ -192,9 +192,6 @@ function resolveCredentialOrDie() {
|
|
|
192
192
|
* `CLAUDE_CODE_SESSION_ID` is set on every process an interactive Claude Code
|
|
193
193
|
* session spawns — this server among them — so no launcher, no dispatch
|
|
194
194
|
* overlay and no agent cooperation is needed for a session to be identifiable.
|
|
195
|
-
* The same variable is what `src/agent/nested-claude-preflight.ts` already
|
|
196
|
-
* relies on to detect a nested session, so this is an established fact about
|
|
197
|
-
* the runtime rather than a new assumption.
|
|
198
195
|
*
|
|
199
196
|
* OPTIONAL, unlike every other env this package reads. A process running
|
|
200
197
|
* outside a Claude Code session has no session to report; it sends no session
|
|
@@ -342,14 +339,14 @@ const boardField = {
|
|
|
342
339
|
.string()
|
|
343
340
|
.min(1)
|
|
344
341
|
.optional()
|
|
345
|
-
.describe("
|
|
342
|
+
.describe("Board id `<repo>:<slug>`; omit for this dispatch's board. Unknown → 404."),
|
|
346
343
|
};
|
|
347
344
|
// The three prose fields of a card, each with ONE job. Shared by issue_create
|
|
348
345
|
// (root + phase children) and issue_edit so the guidance an agent reads is
|
|
349
346
|
// identical wherever it writes the field.
|
|
350
|
-
const TITLE_DESCRIBE = 'Short, specific label naming the domain
|
|
351
|
-
const SUMMARY_DESCRIBE = "1–3 plain-language sentences, no markdown/jargon
|
|
352
|
-
const DESCRIPTION_DESCRIBE = 'The full body ("Context"): evidence, examples, technical detail; markdown, collapsed by default.
|
|
347
|
+
const TITLE_DESCRIBE = 'Short, specific label naming the domain (e.g. "Guest checkout rejects gift-card carts"). Never generic ("Fix bug", "Follow-up").';
|
|
348
|
+
const SUMMARY_DESCRIBE = "1–3 plain-language sentences, no markdown/jargon: what the card is and why it matters. Always shown, never collapsed — not a second title, not a teaser.";
|
|
349
|
+
const DESCRIPTION_DESCRIBE = 'The full body ("Context"): evidence, examples, technical detail; markdown, collapsed by default. Operator questions go in issue_problem. Style: see issue_comment\'s `text` param.';
|
|
353
350
|
// DX-3335 — condensed from the now-deleted danxbot:comment-style skill (PLN-11
|
|
354
351
|
// R-16: fold a skill's rule into the tool description it governs rather than
|
|
355
352
|
// require a separate load). Shared by every prose-writing field below so an
|
|
@@ -412,7 +409,7 @@ strictTool("issue_get",
|
|
|
412
409
|
.optional()
|
|
413
410
|
.describe("Single-id form only. How many comments the page holds (server default 20), the window anchored at the " +
|
|
414
411
|
"NEWEST comment. An out-of-range value (server max 200) is refused by the server with its own 400, " +
|
|
415
|
-
"never silently clamped here. Requires `fields
|
|
412
|
+
"never silently clamped here. Requires `fields:[\"comments\"]`."),
|
|
416
413
|
comments_offset: z
|
|
417
414
|
.number()
|
|
418
415
|
.int()
|
|
@@ -488,7 +485,7 @@ strictTool("issue_create", '`plan` is REQUIRED on every create — no default, n
|
|
|
488
485
|
priority: z
|
|
489
486
|
.union([z.enum(PRIORITY_TIER_WORDS), z.number()])
|
|
490
487
|
.optional()
|
|
491
|
-
.describe('
|
|
488
|
+
.describe('Tier word ("lowest"…"critical", prefer this) or number in [0,6); higher = more urgent. Omit for the route\'s own default.'),
|
|
492
489
|
// DX-3238 — `assigned_agent` is DELIBERATELY left undeclared here, unlike
|
|
493
490
|
// `priority` above. The route accepts it (`ALLOWED_CREATE_KEYS`,
|
|
494
491
|
// create.ts:39-56) but it only matters for the create→`list_id`-lands-
|
|
@@ -525,7 +522,7 @@ strictTool("issue_edit", 'Patch a card. ALLOWED keys: title, summary, descriptio
|
|
|
525
522
|
status: z
|
|
526
523
|
.enum(CHECKLIST_ITEM_STATUSES)
|
|
527
524
|
.optional()
|
|
528
|
-
.describe("Optional full status; omitted → from `checked`.
|
|
525
|
+
.describe("Optional full status; omitted → from `checked`. See tool description for the deferred/📡 rules."),
|
|
529
526
|
detail: z
|
|
530
527
|
.string()
|
|
531
528
|
.optional()
|
|
@@ -552,7 +549,7 @@ strictTool("issue_edit", 'Patch a card. ALLOWED keys: title, summary, descriptio
|
|
|
552
549
|
priority: z
|
|
553
550
|
.union([z.enum(PRIORITY_TIER_WORDS), z.number()])
|
|
554
551
|
.optional()
|
|
555
|
-
.describe('
|
|
552
|
+
.describe('Tier word ("lowest"…"critical", prefer this) or number in [0,6); higher = more urgent — see tool description for why this is the only way to set it.'),
|
|
556
553
|
list_id: z.string().min(1).nullable().optional(),
|
|
557
554
|
triage_enabled: z
|
|
558
555
|
.boolean()
|
|
@@ -647,15 +644,15 @@ const STEP_INPUT = z.lazy(() => z
|
|
|
647
644
|
.int()
|
|
648
645
|
.positive()
|
|
649
646
|
.optional()
|
|
650
|
-
.describe("present ->
|
|
651
|
-
"
|
|
652
|
-
"
|
|
653
|
-
"
|
|
647
|
+
.describe("present -> matches an existing step by this id (400 if not a live child of this exact parent scope, " +
|
|
648
|
+
"never claimed by the fallback below); absent -> matches the first unclaimed same-titled sibling " +
|
|
649
|
+
"under the same parent, else creates new (title-based, never positional — keeps later id-less " +
|
|
650
|
+
"siblings' ids intact on a middle insert). Send ids anyway when identity matters (checked progress, " +
|
|
651
|
+
"a later edit) or siblings share a title."),
|
|
654
652
|
title: z
|
|
655
653
|
.string()
|
|
656
654
|
.min(1)
|
|
657
|
-
.describe("one imperative line
|
|
658
|
-
"in the title itself."),
|
|
655
|
+
.describe("one imperative line — labels (\"1.\", \"2a\") are DERIVED on read, never put one in the title."),
|
|
659
656
|
description: z.string().nullable().optional().describe("markdown detail, or null/omitted for none"),
|
|
660
657
|
steps: z.array(STEP_INPUT).optional().describe("this step's own children, nested — refused past 3 levels total"),
|
|
661
658
|
})
|
|
@@ -669,9 +666,7 @@ strictTool("issue_problem", "A card's PROBLEMS: one statement the operator must
|
|
|
669
666
|
.string()
|
|
670
667
|
.min(1)
|
|
671
668
|
.optional()
|
|
672
|
-
.describe("add/edit; ONE plain sentence, at most 200 characters
|
|
673
|
-
"WHAT IS TO BE DONE — the deed itself, never phrased as a question (\"Rotate the staging DB credential\", " +
|
|
674
|
-
"not \"Should we rotate it?\")."),
|
|
669
|
+
.describe("add/edit; ONE plain sentence, at most 200 characters — see `type` below for question vs action phrasing."),
|
|
675
670
|
context: z
|
|
676
671
|
.string()
|
|
677
672
|
.nullable()
|
|
@@ -697,16 +692,14 @@ strictTool("issue_problem", "A card's PROBLEMS: one statement the operator must
|
|
|
697
692
|
.string()
|
|
698
693
|
.nullable()
|
|
699
694
|
.optional()
|
|
700
|
-
.describe("add/edit. Plain text, 1-3 sentences — never markdown
|
|
701
|
-
"
|
|
702
|
-
"
|
|
703
|
-
"
|
|
704
|
-
"when given (omit/null on a question means none). edit: omit to keep the stored summary, null to clear it " +
|
|
705
|
-
"(refused if the problem is a live action)."),
|
|
695
|
+
.describe("add/edit. Plain text, 1-3 sentences — never markdown (that's `context`'s job). Required+non-empty for an " +
|
|
696
|
+
"action problem (why needed + why you can't do it yourself — see `type`; refused 400 if missing on add or " +
|
|
697
|
+
"cleared on a live action edit); optional for a question. add: sent only when given (omit/null on a " +
|
|
698
|
+
"question means none). edit: omit keeps the stored summary, null clears it."),
|
|
706
699
|
solutions: z
|
|
707
700
|
.array(z.object({ title: z.string().min(1), ...SOLUTION_FIELDS, steps: z.array(STEP_INPUT).optional() }).strict())
|
|
708
701
|
.optional()
|
|
709
|
-
.describe("add only; fields as issue_solution add, INCLUDING steps
|
|
702
|
+
.describe("add only; fields as issue_solution add, INCLUDING steps — an ACTION problem's inline " +
|
|
710
703
|
"solutions must each carry at least one step (refused 400 otherwise); a QUESTION's may carry any number, " +
|
|
711
704
|
"including none."),
|
|
712
705
|
...boardField,
|
|
@@ -715,27 +708,29 @@ strictTool("issue_problem", "A card's PROBLEMS: one statement the operator must
|
|
|
715
708
|
strictTool("issue_solution", "One problem's options, AND one solution's individual " +
|
|
716
709
|
"procedure steps; `problem_id` is REQUIRED (from issue_problem list/add; " +
|
|
717
710
|
"another problem's solution id → 404). add {title, body?, pro?, con?, recommended?, steps?}: title names the " +
|
|
718
|
-
"option
|
|
719
|
-
"
|
|
720
|
-
"
|
|
721
|
-
"
|
|
722
|
-
"
|
|
723
|
-
"
|
|
724
|
-
"
|
|
725
|
-
"
|
|
726
|
-
"
|
|
727
|
-
"
|
|
728
|
-
"
|
|
729
|
-
"
|
|
730
|
-
"
|
|
731
|
-
"
|
|
732
|
-
"
|
|
733
|
-
"
|
|
734
|
-
"
|
|
735
|
-
"
|
|
736
|
-
"
|
|
737
|
-
"
|
|
738
|
-
"
|
|
711
|
+
"option, body/pro/con its markdown detail and case for/against; `steps` authors the WHOLE procedure in one " +
|
|
712
|
+
"call — {title, description?, steps?}[], nestable to 3 levels (4th refused 400, naming the step + limit). " +
|
|
713
|
+
"ACTION problem: `steps` must be non-empty (400 otherwise); QUESTION: may be omitted/empty. Labels " +
|
|
714
|
+
"(\"1\", \"2a\", \"2a.i\") are DERIVED on read from position — never put one in a title. edit :sid {base_hash, " +
|
|
715
|
+
"...changed fields, steps?, steps_base_hash?}: `steps` omitted leaves the procedure untouched; an explicit " +
|
|
716
|
+
"array (incl. [], refused if it leaves an action's solution with zero steps) DIFFS against it — send an " +
|
|
717
|
+
"existing step's `id` to keep it (even while retitling/reordering), omit `id` for a new step; any existing " +
|
|
718
|
+
"step left out is removed. An id-less node matches the first unclaimed same-titled sibling under the same " +
|
|
719
|
+
"parent (never positional), so an id-less MIDDLE insert keeps later id-less siblings' ids — send ids anyway " +
|
|
720
|
+
"when identity matters. `steps_base_hash` REQUIRED whenever `steps` is sent — the solution's current " +
|
|
721
|
+
"`steps_hash`; stale → 409 `stale_steps` with the current tree. Never resend the whole tree to fix one word " +
|
|
722
|
+
"— use add_step/edit_step/remove_step below. remove :sid {base_hash}: stale → 409 `stale_solution` with " +
|
|
723
|
+
"currentHash + currentSolution, merge and retry. At most ONE live recommended per problem — a second → 409 " +
|
|
724
|
+
"naming `recommended_solution_id`. A CHOSEN option's words and WHOLE PROCEDURE are frozen (409 — add a new " +
|
|
725
|
+
"one instead); every steps route below refuses once a decision has chosen this solution.\n\n" +
|
|
726
|
+
"Granular single-step actions, no tree resend: add_step {solution_id, title, description?, parent_step_id?, " +
|
|
727
|
+
"position?} — title is one imperative line (labels derived, never typed); parent_step_id omitted/null = " +
|
|
728
|
+
"top-level; position 1-indexed among current live children, omitted = append, beyond sibling count → 400 " +
|
|
729
|
+
"(never clamped); nesting past depth 3 → 400. edit_step {solution_id, step_id, base_hash, title?, " +
|
|
730
|
+
"description?} — never moves a step. remove_step {solution_id, step_id, base_hash} — soft-deletes it and " +
|
|
731
|
+
"its live children; refused 409 if this solution belongs to an ACTION problem and removing it leaves zero " +
|
|
732
|
+
"live steps. Stale base_hash on a step → 409 `stale_step` with currentHash + currentStep (real derived " +
|
|
733
|
+
"`label`).", {
|
|
739
734
|
id: z.string().min(1),
|
|
740
735
|
action: z.enum(["add", "edit", "remove", "add_step", "edit_step", "remove_step"]),
|
|
741
736
|
problem_id: z.number().int().positive(),
|
|
@@ -833,8 +828,8 @@ strictTool("quality_gate_instruction", "Fetch a named quality gate's current ins
|
|
|
833
828
|
// ---------------- issue_retro ----------------
|
|
834
829
|
strictTool("issue_retro", "Replace the retro block: {good, bad, correctable_danxbot_problem, correctable_danxbot_problem_description, action_item_ids[], commits[], tests[]}. Refuses 409 unless the card is terminal (completed_at or cancelled_at). good/bad upsert; the three list fields REPLACE (soft-delete prior live rows, fresh ordinals). action_item_ids[] entries match <PREFIX>-N. commits[] entries: {sha, subject?}. tests[] is required (empty array = ran none): one row per test GROUP that ran (name the group, not individual unit tests) or per e2e test (kind:'e2e', listed explicitly since few/expensive); each row {name, kind:'group'|'e2e', num_tests, num_passing_tests, duration_ms} required, num_assertions/num_passing_assertions nullable (omit when the runner has no assertion totals).", {
|
|
835
830
|
id: z.string().min(1),
|
|
836
|
-
good: z.string().describe("Free-form markdown (prose or
|
|
837
|
-
bad: z.string().describe("Free-form markdown (prose or
|
|
831
|
+
good: z.string().describe("Free-form markdown (prose or list, they render identically). Style: see issue_comment's `text` param."),
|
|
832
|
+
bad: z.string().describe("Free-form markdown (prose or list, they render identically). Style: see issue_comment's `text` param."),
|
|
838
833
|
correctable_danxbot_problem: z
|
|
839
834
|
.boolean()
|
|
840
835
|
.describe("Required. Did this dispatch hit a problem in danxbot's OWN code/configuration (not \"this card was hard\") that danxbot could change so it stops happening? A true+described answer starts exactly one automated repair (fix or a ready card) — the only reliable channel for a danxbot defect found mid-dispatch. Answer explicitly every time, never a default."),
|
|
@@ -947,14 +942,14 @@ strictTool("plan_get", "Read a plan. Pass `plan_id` to read ANY plan; OMIT to re
|
|
|
947
942
|
.int()
|
|
948
943
|
.nonnegative()
|
|
949
944
|
.optional()
|
|
950
|
-
.describe("Where the `cards` page starts (default 0). Requires `fields
|
|
945
|
+
.describe("Where the `cards` page starts (default 0). Requires `fields:[\"cards\"]`. Page with cards_offset while cards_offset + cards.length < cards_total."),
|
|
951
946
|
cards_limit: z
|
|
952
947
|
.number()
|
|
953
948
|
.int()
|
|
954
949
|
.positive()
|
|
955
950
|
.max(LIST_PAGE_MAX_LIMIT)
|
|
956
951
|
.optional()
|
|
957
|
-
.describe("How many cards one page holds, 1.." + LIST_PAGE_MAX_LIMIT + " (default " + PLAN_GET_CARDS_DEFAULT_LIMIT + "). Requires `fields
|
|
952
|
+
.describe("How many cards one page holds, 1.." + LIST_PAGE_MAX_LIMIT + " (default " + PLAN_GET_CARDS_DEFAULT_LIMIT + "). Requires `fields:[\"cards\"]`."),
|
|
958
953
|
events_limit: z
|
|
959
954
|
.number()
|
|
960
955
|
.int()
|
|
@@ -1049,9 +1044,9 @@ strictTool("plan_update_note", 'Edit a plan note. `content_hash` must be the not
|
|
|
1049
1044
|
content_hash: z.string().describe("The note's `contentHash` from your last read. Required."),
|
|
1050
1045
|
title: z.string().min(1).optional().describe("New title, at most 60 characters. Omit to keep the stored title."),
|
|
1051
1046
|
body: z.string().min(1).optional().describe("New wrap-up, at most 250 characters. Omit to keep the stored body."),
|
|
1052
|
-
card_ids: z.array(z.string().min(1)).optional().describe("
|
|
1053
|
-
record_refs: z.array(z.string().min(1)).optional().describe("
|
|
1054
|
-
section_ids: z.array(z.number().int().positive()).optional().describe("
|
|
1047
|
+
card_ids: z.array(z.string().min(1)).optional().describe("Part of the link-set group — see tool description."),
|
|
1048
|
+
record_refs: z.array(z.string().min(1)).optional().describe("Part of the link-set group — see tool description."),
|
|
1049
|
+
section_ids: z.array(z.number().int().positive()).optional().describe("Part of the link-set group — see tool description."),
|
|
1055
1050
|
}, async (args) => jsonResult(await planUpdateNote(client, args)));
|
|
1056
1051
|
strictTool("plan_delete_note", 'Soft-delete a plan note. `content_hash` must be the note\'s last-read `contentHash` — stale deletes nothing, returns `{error: "stale_plan_note", currentHash, currentTitle, currentBody, currentLinks}` (same shape as `plan_update_note`). Takes an explicit `plan_id` (see `plan_add_note`). Unknown plan, note, or already-deleted note → 404. Returns `{notes_count}`.', {
|
|
1057
1052
|
plan_id: z.number().int().positive().describe("The plan id, from `plan_list`."),
|
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.144",
|
|
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",
|