@thehammer/danx-dashboard-mcp 0.1.126 → 0.1.128
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/index.js +48 -55
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -338,14 +338,14 @@ const boardField = {
|
|
|
338
338
|
.string()
|
|
339
339
|
.min(1)
|
|
340
340
|
.optional()
|
|
341
|
-
.describe("
|
|
341
|
+
.describe("Board id `<repo>:<slug>`; omit for this dispatch's board. Unknown → 404."),
|
|
342
342
|
};
|
|
343
343
|
// The three prose fields of a card, each with ONE job. Shared by issue_create
|
|
344
344
|
// (root + phase children) and issue_edit so the guidance an agent reads is
|
|
345
345
|
// identical wherever it writes the field.
|
|
346
|
-
const TITLE_DESCRIBE = 'Short, specific label naming the domain
|
|
347
|
-
const SUMMARY_DESCRIBE = "1–3 plain-language sentences, no markdown/jargon
|
|
348
|
-
const DESCRIPTION_DESCRIBE = 'The full body ("Context"): evidence, examples, technical detail; markdown, collapsed by default.
|
|
346
|
+
const TITLE_DESCRIBE = 'Short, specific label naming the domain (e.g. "Guest checkout rejects carts holding a gift card"). Never generic ("Fix bug", "Follow-up").';
|
|
347
|
+
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 for the description.";
|
|
348
|
+
const DESCRIPTION_DESCRIBE = 'The full body ("Context"): evidence, examples, technical detail; markdown, collapsed by default. Operator questions go in issue_problem, not here.';
|
|
349
349
|
// ---------------- issue_list ----------------
|
|
350
350
|
strictTool("issue_list",
|
|
351
351
|
// DX-2735: trimmed to pay for the problem tools inside the work-profile
|
|
@@ -398,7 +398,7 @@ strictTool("issue_get",
|
|
|
398
398
|
.optional()
|
|
399
399
|
.describe("Single-id form only. How many comments the page holds (server default 20), the window anchored at the " +
|
|
400
400
|
"NEWEST comment. An out-of-range value (server max 200) is refused by the server with its own 400, " +
|
|
401
|
-
"never silently clamped here. Requires `fields
|
|
401
|
+
"never silently clamped here. Requires `fields:[\"comments\"]`."),
|
|
402
402
|
comments_offset: z
|
|
403
403
|
.number()
|
|
404
404
|
.int()
|
|
@@ -410,8 +410,8 @@ strictTool("issue_get",
|
|
|
410
410
|
...boardField,
|
|
411
411
|
}, async (args) => jsonResult(await issueGet(client, args)));
|
|
412
412
|
// ---------------- issue_create ----------------
|
|
413
|
-
strictTool("issue_create", '`plan` is REQUIRED on every create (DX-3006):
|
|
414
|
-
'Create a card via POST /api/issues. Board-scoped; see `board`. type=Epic REQUIRES non-empty phase_children[] (epic and phases inserted in one transaction; children get the epic as parent); other types refuse phase_children[] (400). Status starts at Review. `list_id` places the card straight into a column — a board_lists id or the list\'s display NAME (case-insensitive, emoji-tolerant): a `ready`-type queue lands it in ToDo, a `completed` list in Done, with no follow-up transition. Not valid on Epic; unknown name/id → 400. `quality_gates` names the gates this card carries BEYOND the board\'s default set for its type — one `{gate, note?}` each. Omit
|
|
413
|
+
strictTool("issue_create", '`plan` is REQUIRED on every create (DX-3006, no default/inference): "mine" puts the card on THIS session\'s connected plan; null = deliberately no plan. "mine" while connected to none is refused (409 session_not_connected), creating NO card; a plan id is not accepted — a card is only ever created onto your own connected plan. Replaces the plan_add_card follow-up at creation time; plan_add_card still exists for putting an EXISTING card on a plan. ' +
|
|
414
|
+
'Create a card via POST /api/issues. Board-scoped; see `board`. type=Epic REQUIRES non-empty phase_children[] (epic and phases inserted in one transaction; children get the epic as parent); other types refuse phase_children[] (400). Status starts at Review. `list_id` places the card straight into a column — a board_lists id or the list\'s display NAME (case-insensitive, emoji-tolerant): a `ready`-type queue lands it in ToDo, a `completed` list in Done, with no follow-up transition. Not valid on Epic; unknown name/id → 400. `quality_gates` names the gates this card carries BEYOND the board\'s default set for its type — one `{gate, note?}` each. Omit for just the board defaults — an unnamed gate is simply not on the card. Add one later with `issue_quality_gate`. ALWAYS pass `triage_enabled` explicitly on the root card and every phase child: true only when it should enter automatic triage/dispatch without human review; absent → false.', {
|
|
415
415
|
type: z.enum(ISSUE_TYPES),
|
|
416
416
|
title: z.string().min(1).describe(TITLE_DESCRIBE),
|
|
417
417
|
summary: z.string().min(1).optional().describe(SUMMARY_DESCRIBE),
|
|
@@ -423,7 +423,7 @@ strictTool("issue_create", '`plan` is REQUIRED on every create (DX-3006): pass "
|
|
|
423
423
|
plan: z
|
|
424
424
|
.literal("mine")
|
|
425
425
|
.nullable()
|
|
426
|
-
.describe('
|
|
426
|
+
.describe('REQUIRED, decide per card — see tool description. "mine" = this session\'s connected plan; null = deliberately none; no plan id accepted.'),
|
|
427
427
|
parent_id: z.string().nullable().optional(),
|
|
428
428
|
ac: z.array(z.object({ title: z.string().min(1) }).strict()).optional(),
|
|
429
429
|
effort_level: z.enum(EFFORT_VALUES).nullable().optional(),
|
|
@@ -474,7 +474,7 @@ strictTool("issue_create", '`plan` is REQUIRED on every create (DX-3006): pass "
|
|
|
474
474
|
priority: z
|
|
475
475
|
.union([z.enum(PRIORITY_TIER_WORDS), z.number()])
|
|
476
476
|
.optional()
|
|
477
|
-
.describe('
|
|
477
|
+
.describe('Tier word ("lowest"…"critical", prefer this) or number in [0,6); higher = more urgent. Omit for the route\'s own default.'),
|
|
478
478
|
// DX-3238 — `assigned_agent` is DELIBERATELY left undeclared here, unlike
|
|
479
479
|
// `priority` above. The route accepts it (`ALLOWED_CREATE_KEYS`,
|
|
480
480
|
// create.ts:39-56) but it only matters for the create→`list_id`-lands-
|
|
@@ -490,7 +490,7 @@ strictTool("issue_create", '`plan` is REQUIRED on every create (DX-3006): pass "
|
|
|
490
490
|
...boardField,
|
|
491
491
|
}, async (args) => jsonResult(await issueCreate(client, args, config.board)));
|
|
492
492
|
// ---------------- issue_edit ----------------
|
|
493
|
-
strictTool("issue_edit", 'Patch a card via PATCH /api/issues/:id/edit. ALLOWED keys: title, summary, description, ac, checklists, effort_level, parent_id, priority, list_id, triage_enabled, type, content_hash. Any other key (lifecycle, triage, dependencies, retro, blocked) is refused 400 with offending_keys[] naming the right tool: issue_transition / issue_triage / issue_comment / issue_dependency / issue_problem / issue_retro. `type`: Story/Bug/Chore = eligible for autonomous pickup; Task or a container (Epic/Feature) removes that eligibility
|
|
493
|
+
strictTool("issue_edit", 'Patch a card via PATCH /api/issues/:id/edit. ALLOWED keys: title, summary, description, ac, checklists, effort_level, parent_id, priority, list_id, triage_enabled, type, content_hash. Any other key (lifecycle, triage, dependencies, retro, blocked) is refused 400 with offending_keys[] naming the right tool: issue_transition / issue_triage / issue_comment / issue_dependency / issue_problem / issue_retro. `type`: Story/Bug/Chore = eligible for autonomous pickup; Task or a container (Epic/Feature) removes that eligibility. `priority` (tier word "lowest"–"critical", or number 0–6, higher = more urgent) is the ONLY way to set priority; a "Priority:" line does nothing. CHECKLISTS: each item has one status `incomplete|failing|passing|cancelled|deferred`; `deferred` (work done, real-world check outstanding) REQUIRES `detail`, and a `📡`-prefixed item can never be `passing`. `ac` edits the default "Acceptance Criteria" checklist (items matched by check_item_id, else exact title); `checklists` REPLACES every named checklist with full status control (`{name, items:[{label, detail?, status}]}`). Send `ac` OR `checklists`, not both (400). `list_id` pins the card to a list by id or display NAME; its type must match the card\'s current derived status (400 otherwise — e.g. ready the card first before pinning it to a `ready`-type list); null clears the pin. `content_hash` (DX-2741) is the card\'s optimistic-concurrency token — REQUIRED whenever the edit touches `title` / `description` / `checklists` (NOT `ac`, which keeps its own check_item_id/title diffing): missing → 400, stale → 409 `stale_issue_content` carrying `currentHash` + `currentTitle` + `currentDescription`. Read it off `issue_get`/`issue_list`\'s `content_hash` scalar (present even minimal); on 409, re-`issue_get` and retry with the fresh hash — never blindly.', {
|
|
494
494
|
id: z.string().min(1),
|
|
495
495
|
title: z.string().min(1).optional().describe(TITLE_DESCRIBE),
|
|
496
496
|
summary: z
|
|
@@ -511,7 +511,7 @@ strictTool("issue_edit", 'Patch a card via PATCH /api/issues/:id/edit. ALLOWED k
|
|
|
511
511
|
status: z
|
|
512
512
|
.enum(CHECKLIST_ITEM_STATUSES)
|
|
513
513
|
.optional()
|
|
514
|
-
.describe("Optional full status; omitted → from `checked`.
|
|
514
|
+
.describe("Optional full status; omitted → from `checked`. See tool description for the deferred/📡 rules."),
|
|
515
515
|
detail: z
|
|
516
516
|
.string()
|
|
517
517
|
.optional()
|
|
@@ -538,7 +538,7 @@ strictTool("issue_edit", 'Patch a card via PATCH /api/issues/:id/edit. ALLOWED k
|
|
|
538
538
|
priority: z
|
|
539
539
|
.union([z.enum(PRIORITY_TIER_WORDS), z.number()])
|
|
540
540
|
.optional()
|
|
541
|
-
.describe('
|
|
541
|
+
.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.'),
|
|
542
542
|
list_id: z.string().min(1).nullable().optional(),
|
|
543
543
|
triage_enabled: z
|
|
544
544
|
.boolean()
|
|
@@ -597,7 +597,7 @@ const CHECKLIST_ITEM_INPUT = z
|
|
|
597
597
|
status: z.enum(CHECKLIST_ITEM_STATUSES).optional(),
|
|
598
598
|
})
|
|
599
599
|
.strict();
|
|
600
|
-
strictTool("issue_checklist", "Targeted checklist CUD via /api/issues/:id/checklists[/:cid[/items[/:iid]]] (DX-1362). Mutates ONE checklist/item without the wholesale `issue_edit({checklists})` replace, which DROPS any checklist you omit and churns every item id (orphaning its Trello mirror) — prefer this for single-item changes. Action-dispatched: add_list (POST :id/checklists {name, items?}) — create a named checklist, optionally with initial items; update_list (PATCH :id/checklists/:cid {name}) — rename; remove_list (DELETE :id/checklists/:cid) — soft-delete the checklist (audit trail preserved); add_item (POST :id/checklists/:cid/items {label, detail?, status?}) — append an item (status defaults `incomplete`); update_item (PATCH :id/checklists/:cid/items/:iid {label?, detail?, status?}) — change ONLY the fields you pass, in place (keeps id + Trello link; ≥1 field required); remove_item (DELETE :id/checklists/:cid/items/:iid) — soft-delete one item. Status: incomplete|failing|passing|cancelled|deferred (terminal = passing|cancelled|deferred;
|
|
600
|
+
strictTool("issue_checklist", "Targeted checklist CUD via /api/issues/:id/checklists[/:cid[/items[/:iid]]] (DX-1362). Mutates ONE checklist/item without the wholesale `issue_edit({checklists})` replace, which DROPS any checklist you omit and churns every item id (orphaning its Trello mirror) — prefer this for single-item changes. Action-dispatched: add_list (POST :id/checklists {name, items?}) — create a named checklist, optionally with initial items; update_list (PATCH :id/checklists/:cid {name}) — rename; remove_list (DELETE :id/checklists/:cid) — soft-delete the checklist (audit trail preserved); add_item (POST :id/checklists/:cid/items {label, detail?, status?}) — append an item (status defaults `incomplete`); update_item (PATCH :id/checklists/:cid/items/:iid {label?, detail?, status?}) — change ONLY the fields you pass, in place (keeps id + Trello link; ≥1 field required); remove_item (DELETE :id/checklists/:cid/items/:iid) — soft-delete one item. Status: incomplete|failing|passing|cancelled|deferred (terminal = passing|cancelled|deferred; `deferred` = work done but a real-world/post-deploy check still outstanding, REQUIRES a non-empty `detail`). checklist_id is required for every action except add_list; item_id for update_item/remove_item. Each returns the {ok,status,body} envelope; unknown card/checklist/item → 404, invalid status → 400. ADDITIVE — `issue_edit({checklists})` still works for bulk authoring.", {
|
|
601
601
|
id: z.string().min(1),
|
|
602
602
|
action: z.enum([
|
|
603
603
|
"add_list",
|
|
@@ -648,7 +648,7 @@ const STEP_INPUT = z.lazy(() => z
|
|
|
648
648
|
steps: z.array(STEP_INPUT).optional().describe("this step's own children, nested — refused past 3 levels total"),
|
|
649
649
|
})
|
|
650
650
|
.strict());
|
|
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
|
|
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, and put any investigation detail in `context` (markdown, no cap) instead of running it on — e.g. statement \"Which cache should we use?\", context \"Redis fits the read-heavy path; see benchmark in #123.\" not a run-on statement listing the whole investigation. Every problem is also a `type` with a `summary` distinct from `context` — read both fields' own descriptions below before your first `add`.", {
|
|
652
652
|
id: z.string().min(1),
|
|
653
653
|
action: z.enum(["list", "add", "edit", "remove"]),
|
|
654
654
|
problem_id: z.number().int().positive().optional().describe("edit/remove"),
|
|
@@ -657,9 +657,7 @@ strictTool("issue_problem", "A card's PROBLEMS via /api/issues/:id/problems[/:pi
|
|
|
657
657
|
.string()
|
|
658
658
|
.min(1)
|
|
659
659
|
.optional()
|
|
660
|
-
.describe("add/edit; ONE plain sentence, at most 200 characters
|
|
661
|
-
"WHAT IS TO BE DONE — the deed itself, never phrased as a question (\"Rotate the staging DB credential\", " +
|
|
662
|
-
"not \"Should we rotate it?\")."),
|
|
660
|
+
.describe("add/edit; ONE plain sentence, at most 200 characters — see `type` below for question vs action phrasing."),
|
|
663
661
|
context: z
|
|
664
662
|
.string()
|
|
665
663
|
.nullable()
|
|
@@ -671,33 +669,28 @@ strictTool("issue_problem", "A card's PROBLEMS via /api/issues/:id/problems[/:pi
|
|
|
671
669
|
type: z
|
|
672
670
|
.enum(["question", "action"])
|
|
673
671
|
.optional()
|
|
674
|
-
.describe("add only.
|
|
675
|
-
"
|
|
676
|
-
"
|
|
677
|
-
"
|
|
678
|
-
"
|
|
679
|
-
"
|
|
680
|
-
"
|
|
681
|
-
"
|
|
682
|
-
"
|
|
683
|
-
"
|
|
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."),
|
|
672
|
+
.describe("add only. Test: could I do this myself if I tried harder, missing only a decision? If yes, \"question\": " +
|
|
673
|
+
"statement is the question (one plain sentence); summary optional (why it matters); context is the evidence; " +
|
|
674
|
+
"solutions[] are candidate ANSWERS with pro/con, done the moment the operator picks one. If the blocker is " +
|
|
675
|
+
"access, credentials, hardware, a human's authority, or a system you genuinely cannot reach, \"action\": " +
|
|
676
|
+
"statement is WHAT IS TO BE DONE — the deed itself, never phrased as a question (\"Rotate the staging DB " +
|
|
677
|
+
"credential\", not \"Should we rotate it?\"); summary is REQUIRED, saying WHY THE ACTION IS NEEDED AND WHY " +
|
|
678
|
+
"YOU CANNOT DO IT YOURSELF (an add with no summary is refused 400 — proves this isn't you being lazy); " +
|
|
679
|
+
"context is whatever detail the person needs to carry it out; solutions[] are the possible ROUTES a person " +
|
|
680
|
+
"could take, and EACH MUST CARRY AT LEAST ONE STEP (refused 400 otherwise). Omitted defaults to \"question\" " +
|
|
681
|
+
"— decide deliberately every time."),
|
|
687
682
|
summary: z
|
|
688
683
|
.string()
|
|
689
684
|
.nullable()
|
|
690
685
|
.optional()
|
|
691
|
-
.describe("add/edit. Plain text, 1-3 sentences — never markdown
|
|
692
|
-
"
|
|
693
|
-
"
|
|
694
|
-
"
|
|
695
|
-
"when given (omit/null on a question means none). edit: omit to keep the stored summary, null to clear it " +
|
|
696
|
-
"(refused if the problem is a live action)."),
|
|
686
|
+
.describe("add/edit. Plain text, 1-3 sentences — never markdown (that's `context`'s job). Required+non-empty for an " +
|
|
687
|
+
"action problem (why needed + why you can't do it yourself — see `type`; refused 400 if missing on add or " +
|
|
688
|
+
"cleared on a live action edit); optional for a question. add: sent only when given (omit/null on a " +
|
|
689
|
+
"question means none). edit: omit keeps the stored summary, null clears it."),
|
|
697
690
|
solutions: z
|
|
698
691
|
.array(z.object({ title: z.string().min(1), ...SOLUTION_FIELDS, steps: z.array(STEP_INPUT).optional() }).strict())
|
|
699
692
|
.optional()
|
|
700
|
-
.describe("add only; fields as issue_solution add, INCLUDING steps
|
|
693
|
+
.describe("add only; fields as issue_solution add, INCLUDING steps — an ACTION problem's inline " +
|
|
701
694
|
"solutions must each carry at least one step (refused 400 otherwise); a QUESTION's may carry any number, " +
|
|
702
695
|
"including none."),
|
|
703
696
|
...boardField,
|
|
@@ -708,7 +701,7 @@ strictTool("issue_solution", "One problem's options via /api/issues/:id/problems
|
|
|
708
701
|
"another problem's solution id → 404). add {title, body?, pro?, con?, recommended?, steps?}: title names the " +
|
|
709
702
|
"option/route, body is its markdown detail, pro/con the case for and against, `steps` is its WHOLE procedure " +
|
|
710
703
|
"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).
|
|
704
|
+
"400 naming the offending step and the limit). Under an ACTION problem, `steps` must be non-empty " +
|
|
712
705
|
"(refused 400 otherwise); under a QUESTION it may be omitted or empty. Labels (\"1\", \"2a\", \"2a.i\") are " +
|
|
713
706
|
"ALWAYS DERIVED on read from position — never put one in a title yourself. edit :sid {base_hash, ...only the " +
|
|
714
707
|
"changed fields, steps?, steps_base_hash?}: `steps` omitted leaves the stored procedure untouched, an explicit " +
|
|
@@ -725,8 +718,8 @@ strictTool("issue_solution", "One problem's options via /api/issues/:id/problems
|
|
|
725
718
|
"`recommended_solution_id`. A CHOSEN option's words are frozen (409 — add a new one instead), and so is its " +
|
|
726
719
|
"WHOLE PROCEDURE — every steps route below also refuses once a decision has chosen this solution.\n\n" +
|
|
727
720
|
"Granular single-step actions — change ONE step without resending the tree: add_step {solution_id, title, " +
|
|
728
|
-
"description?, parent_step_id?, position?} — title is ONE imperative line
|
|
729
|
-
"
|
|
721
|
+
"description?, parent_step_id?, position?} — title is ONE imperative line (labels are derived, never typed, " +
|
|
722
|
+
"same rule as above); parent_step_id omitted/null = top-level; position is 1-indexed among the parent's current " +
|
|
730
723
|
"live children, omitted = append, and a position beyond the current sibling count is refused (400), never " +
|
|
731
724
|
"silently clamped to append; nesting past depth 3 → 400. edit_step {solution_id, step_id, base_hash, title?, " +
|
|
732
725
|
"description?} — never moves a step (no parent_step_id/position here). remove_step {solution_id, step_id, " +
|
|
@@ -764,7 +757,7 @@ strictTool("issue_solution", "One problem's options via /api/issues/:id/problems
|
|
|
764
757
|
...boardField,
|
|
765
758
|
}, async (args) => jsonResult(await issueSolution(client, args)));
|
|
766
759
|
// ---------------- issue_dependency ----------------
|
|
767
|
-
strictTool("issue_dependency", 'Dependency CRUD via /api/issues/:id/dependencies[/:did]. action=add → POST {kind, target_id, reason} where kind ∈ {depends_on, conflict_on}. depends_on adds are CYCLE-CHECKED (BFS from target back to source — 409 if loop). Idempotent: re-adding a live triple returns the existing id. Self-loops refuse 409. action=remove → DELETE /:did. The server REQUIRES the literal reason="recorded_in_error" on removal
|
|
760
|
+
strictTool("issue_dependency", 'Dependency CRUD via /api/issues/:id/dependencies[/:did]. action=add → POST {kind, target_id, reason} where kind ∈ {depends_on, conflict_on}. depends_on adds are CYCLE-CHECKED (BFS from target back to source — 409 if loop). Idempotent: re-adding a live triple returns the existing id. Self-loops refuse 409. action=remove → DELETE /:did. The server REQUIRES the literal reason="recorded_in_error" on removal; this MCP boundary hardcodes it, so callers do not pass reason on remove. This is the ONLY mechanism the dispatch picker enforces to sequence one card after another — leaving a card at Review/held (e.g. an issue_triage "keep" verdict) is NOT a substitute.', {
|
|
768
761
|
id: z.string().min(1),
|
|
769
762
|
action: z.enum(["add", "remove"]),
|
|
770
763
|
kind: z.enum(["depends_on", "conflict_on"]).optional(),
|
|
@@ -801,7 +794,7 @@ strictTool("issue_quality_gate", "Put one quality gate ON a card, or take it OFF
|
|
|
801
794
|
...boardField,
|
|
802
795
|
}, async (args) => jsonResult(await issueQualityGate(client, args)));
|
|
803
796
|
// ---------------- issue_quality_gate_verdict ----------------
|
|
804
|
-
strictTool("issue_quality_gate_verdict", "Stamp an operator MANUAL quality-gate VERDICT via PATCH /api/issues/:id/quality-gates/:gate {status, message} — the same write the dashboard Gates-tab Pass / Fail / Revert controls perform (DX-1373). SIBLING of `issue_quality_gate`, not a replacement: that one puts a gate ON or OFF the card (does this gate apply at all), THIS one records the VERDICT (did it pass) — POST vs PATCH on the same resource, neither substitutes for the other. **Use this to close out a card you picked up with `issue_transition pickup {manual:true}
|
|
797
|
+
strictTool("issue_quality_gate_verdict", "Stamp an operator MANUAL quality-gate VERDICT via PATCH /api/issues/:id/quality-gates/:gate {status, message} — the same write the dashboard Gates-tab Pass / Fail / Revert controls perform (DX-1373). SIBLING of `issue_quality_gate`, not a replacement: that one puts a gate ON or OFF the card (does this gate apply at all), THIS one records the VERDICT (did it pass) — POST vs PATCH on the same resource, neither substitutes for the other. **Use this to close out a card you picked up with `issue_transition pickup {manual:true}`**: `issue_transition complete` REFUSES 409 (`failed_gate: \"quality_gate_post\"`, `failed_post_gates[]`) while any required POST gate is not `pass` — without a verdict a manually-claimed card strands In Progress, stalling other cards via its `conflict_on`/`waiting_on` edges. `status`: `pass` | `fail` | `pending` (revert a prior verdict, clears the message). `message` is the accountability record for the override, REQUIRED at >= 20 characters for `pass`/`fail` (shorter → 400), ignored for `pending`. Record the REAL reviewer finding, not a rubber stamp. A manual verdict is a PURE row write: no side effects — a manual `fail` never blocks the card and a manual `pass` never releases a dispatch. Unknown gate → 400; bad status → 400; unknown card → 404. Board-scoped; see `board`.", {
|
|
805
798
|
id: z.string().min(1),
|
|
806
799
|
gate: z.enum([
|
|
807
800
|
"plan-dependency",
|
|
@@ -822,7 +815,7 @@ strictTool("issue_retro", "Replace the retro block via PUT /api/issues/:id/retro
|
|
|
822
815
|
bad: z.string(),
|
|
823
816
|
correctable_danxbot_problem: z
|
|
824
817
|
.boolean()
|
|
825
|
-
.describe("Required
|
|
818
|
+
.describe("Required every time, answered honestly — see tool description. Determines whether the description field below must be filled or must be empty."),
|
|
826
819
|
correctable_danxbot_problem_description: z
|
|
827
820
|
.string()
|
|
828
821
|
.describe("Required. Non-empty (naming the concrete problem) when correctable_danxbot_problem is true; must be empty when it is false."),
|
|
@@ -916,7 +909,7 @@ strictTool("plan_list", "List every plan via GET /api/plans (DX-2683), and learn
|
|
|
916
909
|
.optional()
|
|
917
910
|
.describe("Filter to one computed status: awaiting-session, planning, building, complete. Omit for every plan."),
|
|
918
911
|
}, async (args) => jsonResult(await planList(client, args)));
|
|
919
|
-
strictTool("plan_get", "Read a plan via GET /api/plans (DX-2683). Pass `plan_id` to read ANY plan; OMIT to read the plan this session is connected to — omitting while connected to none fails loud `{error: \"session_not_connected\"}`, connect first. A BARE call (no `fields`) returns ONLY the plan's cheap scalars: `{plan, boards, cardCount, bucketCounts, status, session, sessionListenerAttached, available_field_groups}` — no cards, records, or architecture body. `plan` carries `{id, ref, name, createdAt}`; `ref` is the plan's short reference (`PLN-<id>`) — cite that, not the bare id. `status` (DX-2834) is computed fresh on every read, never stored — see `plan_list` for the four values and what each means. Pass `fields` to opt into: `cards` (member cards, PAGED: `cards_offset` (default 0) and `cards_limit` (1.." + LIST_PAGE_MAX_LIMIT + ", default " + PLAN_GET_CARDS_DEFAULT_LIMIT + ") pick the page, in stable card-reference order (board prefix, then card number
|
|
912
|
+
strictTool("plan_get", "Read a plan via GET /api/plans (DX-2683). Pass `plan_id` to read ANY plan; OMIT to read the plan this session is connected to — omitting while connected to none fails loud `{error: \"session_not_connected\"}`, connect first. A BARE call (no `fields`) returns ONLY the plan's cheap scalars: `{plan, boards, cardCount, bucketCounts, status, session, sessionListenerAttached, available_field_groups}` — no cards, records, or architecture body. `plan` carries `{id, ref, name, createdAt}`; `ref` is the plan's short reference (`PLN-<id>`) — cite that, not the bare id. `status` (DX-2834) is computed fresh on every read, never stored — see `plan_list` for the four values and what each means. Pass `fields` to opt into: `cards` (member cards, PAGED: `cards_offset` (default 0) and `cards_limit` (1.." + LIST_PAGE_MAX_LIMIT + ", default " + PLAN_GET_CARDS_DEFAULT_LIMIT + ") pick the page, in stable card-reference order (board prefix, then card number); response carries `cards_total`/`cards_offset` — page while cards_offset + cards.length < cards_total; either paging arg without `fields: [\"cards\"]` is a 400), `records` (every goal+rule+caveat, keyed by kind) or `records:goal` / `records:rule` / `records:caveat` (one kind, cheaper), `architecture` (`{sections: [{id, planId, contentHash, title, content, sortOrder, createdAt, updatedAt}]}`), `sessions` (every session connected to the plan), `events` (DX-2987/DX-3027 — the plan's durable event ledger: every human action and every bridge message recorded on it, cursor-paged newest first via `events_limit` (1.." + PLAN_GET_EVENTS_MAX_LIMIT + ", default " + PLAN_GET_EVENTS_DEFAULT_LIMIT + ") and `events_before` (an opaque cursor — pass a previous page's `next_cursor` to read older; omit for the newest page); filter with `events_kinds` (one or more, see its own enum), `events_origin` (one, see its own enum), `events_writer` (exact writer name); every `events_*` param without `fields: [\"events\"]` is a 400, same as the `cards_*` params above; response carries `events: {items: [{id, at, kind, writer, origin, originSessionId, targetSessionId, cardId, cardTitle, boardId, detail}], next_cursor}` — `next_cursor` is `null` on the last page; an event on a card whose board you cannot read is left out, plan-level events are always visible). `session`/`sessionListenerAttached` and `available_field_groups` ride every response regardless. `sessionListenerAttached` is `false` for a few seconds right after `plan_connect` while the plugin's event bridge starts; still `false` after that while connected means the plan's card events are not reaching you — tell the operator. ALWAYS `plan_get`/`plan_get_architecture_section` immediately before `plan_update_architecture_section`/`plan_delete_architecture_section` and pass the returned `contentHash` back as `base_hash`.", {
|
|
920
913
|
plan_id: z
|
|
921
914
|
.number()
|
|
922
915
|
.int()
|
|
@@ -932,39 +925,39 @@ strictTool("plan_get", "Read a plan via GET /api/plans (DX-2683). Pass `plan_id`
|
|
|
932
925
|
.int()
|
|
933
926
|
.nonnegative()
|
|
934
927
|
.optional()
|
|
935
|
-
.describe("Where the `cards` page starts (default 0). Requires `fields
|
|
928
|
+
.describe("Where the `cards` page starts (default 0). Requires `fields:[\"cards\"]`. Page with cards_offset while cards_offset + cards.length < cards_total."),
|
|
936
929
|
cards_limit: z
|
|
937
930
|
.number()
|
|
938
931
|
.int()
|
|
939
932
|
.positive()
|
|
940
933
|
.max(LIST_PAGE_MAX_LIMIT)
|
|
941
934
|
.optional()
|
|
942
|
-
.describe("How many cards one page holds, 1.." + LIST_PAGE_MAX_LIMIT + " (default " + PLAN_GET_CARDS_DEFAULT_LIMIT + "). Requires `fields
|
|
935
|
+
.describe("How many cards one page holds, 1.." + LIST_PAGE_MAX_LIMIT + " (default " + PLAN_GET_CARDS_DEFAULT_LIMIT + "). Requires `fields:[\"cards\"]`."),
|
|
943
936
|
events_limit: z
|
|
944
937
|
.number()
|
|
945
938
|
.int()
|
|
946
939
|
.positive()
|
|
947
940
|
.max(PLAN_GET_EVENTS_MAX_LIMIT)
|
|
948
941
|
.optional()
|
|
949
|
-
.describe("DX-3027 — how many ledger events one page holds, 1.." + PLAN_GET_EVENTS_MAX_LIMIT + " (default " + PLAN_GET_EVENTS_DEFAULT_LIMIT + "). Requires `fields
|
|
942
|
+
.describe("DX-3027 — how many ledger events one page holds, 1.." + PLAN_GET_EVENTS_MAX_LIMIT + " (default " + PLAN_GET_EVENTS_DEFAULT_LIMIT + "). Requires `fields:[\"events\"]`."),
|
|
950
943
|
events_before: z
|
|
951
944
|
.string()
|
|
952
945
|
.min(1)
|
|
953
946
|
.optional()
|
|
954
|
-
.describe("
|
|
947
|
+
.describe("an opaque cursor from a previous page's `next_cursor`. Omit for the newest page. Requires `fields:[\"events\"]`."),
|
|
955
948
|
events_kinds: z
|
|
956
949
|
.array(z.enum(PLAN_EVENT_KINDS))
|
|
957
950
|
.optional()
|
|
958
|
-
.describe("
|
|
951
|
+
.describe("only these event kinds. Omit for every kind. Requires `fields:[\"events\"]`."),
|
|
959
952
|
events_origin: z
|
|
960
953
|
.enum(PLAN_EVENT_ORIGINS)
|
|
961
954
|
.optional()
|
|
962
|
-
.describe("
|
|
955
|
+
.describe("only events with this origin. Omit for every origin. Requires `fields:[\"events\"]`."),
|
|
963
956
|
events_writer: z
|
|
964
957
|
.string()
|
|
965
958
|
.min(1)
|
|
966
959
|
.optional()
|
|
967
|
-
.describe("
|
|
960
|
+
.describe("only events with this exact writer name. Omit for every writer. Requires `fields:[\"events\"]`."),
|
|
968
961
|
}, async (args) => jsonResult(await planGet(client, args)));
|
|
969
962
|
strictTool("plan_create", "Create a new, empty plan via POST /api/plans (DX-2531). GLOBAL — a plan is not board-scoped, and this call adds no cards, no records, and no architecture sections; it does NOT connect any session to the new plan (call `plan_connect` separately, exactly as adding a card to a plan is its own separate step). Returns `{ok, status, body: {plan: {id, ref, name, createdAt}}}` — `ref` is the plan's short reference (`PLN-<id>`). Use the returned `plan.id` with `plan_connect` to start working on it, or with `plan_get({plan_id})` to browse it.", {
|
|
970
963
|
name: z.string().min(1).describe("The plan's name — shown in the Plans list."),
|
|
@@ -1034,9 +1027,9 @@ strictTool("plan_update_note", 'Edit a plan note, via PATCH /api/plans/:plan_id/
|
|
|
1034
1027
|
content_hash: z.string().describe("The note's `contentHash` from your last read. Required."),
|
|
1035
1028
|
title: z.string().min(1).optional().describe("New title, at most 60 characters. Omit to keep the stored title."),
|
|
1036
1029
|
body: z.string().min(1).optional().describe("New wrap-up, at most 250 characters. Omit to keep the stored body."),
|
|
1037
|
-
card_ids: z.array(z.string().min(1)).optional().describe("
|
|
1038
|
-
record_refs: z.array(z.string().min(1)).optional().describe("
|
|
1039
|
-
section_ids: z.array(z.number().int().positive()).optional().describe("
|
|
1030
|
+
card_ids: z.array(z.string().min(1)).optional().describe("Part of the link-set group — see tool description."),
|
|
1031
|
+
record_refs: z.array(z.string().min(1)).optional().describe("Part of the link-set group — see tool description."),
|
|
1032
|
+
section_ids: z.array(z.number().int().positive()).optional().describe("Part of the link-set group — see tool description."),
|
|
1040
1033
|
}, async (args) => jsonResult(await planUpdateNote(client, args)));
|
|
1041
1034
|
strictTool("plan_delete_note", 'Soft-delete a plan note, via DELETE /api/plans/:plan_id/notes/:note_id (DX-2915). `content_hash` must be the note\'s `contentHash` from your last read; a stale hash deletes nothing and returns `{error: "stale_plan_note", currentHash, currentTitle, currentBody, currentLinks}` — the same shape `plan_update_note` uses. TAKES AN EXPLICIT `plan_id`, same reason as `plan_add_note`. Unknown plan, unknown note, or an already-deleted note → 404. DX-3072 — returns `{notes_count}`, the plan\'s remaining live note count, not the latest page.', {
|
|
1042
1035
|
plan_id: z.number().int().positive().describe("The plan id, from `plan_list`."),
|
|
@@ -1121,7 +1114,7 @@ strictTool("failure_category_update", "Patch an existing failure category via PA
|
|
|
1121
1114
|
expectedRate: expectedRateField,
|
|
1122
1115
|
}, async (args) => jsonResult(await failureCategoryUpdate(client, args)));
|
|
1123
1116
|
// ---------------- dispatch_transcript_search (DX-3221) ----------------
|
|
1124
|
-
strictTool("dispatch_transcript_search", "Search or tail ANOTHER dispatch's stored JSONL session transcript via
|
|
1117
|
+
strictTool("dispatch_transcript_search", "Search or tail ANOTHER dispatch's stored JSONL session transcript via GET /api/dispatches/:id/logs (DX-1682/DX-1484/DX-3221) — no new server route, only in-process search/windowing over the transcript already captured there. Prefer this over reading the transcript file directly: no filesystem access needed, and a single JSONL entry can be far longer than a line-based file read can sub-page — this tool searches the string itself and returns only a bounded excerpt AROUND a match, never the whole line. With `pattern`: returns up to `maxMatches` matching lines (case-insensitive regex), each a windowed excerpt of up to `contextChars` characters centered on the first match. Without `pattern`: returns the most recent `tail` lines instead, each capped at `contextChars`. `truncated: true` marks a capped excerpt either way, never a thrown error. `dispatchId` is a real `dispatches.id` — exactly the ids a failure-repair card's own body already lists under \"dispatches that hit it\".", {
|
|
1125
1118
|
dispatchId: z.string().min(1).describe("The dispatch id whose transcript to search — from a failure-repair card's own body, or any other dispatch id you already have."),
|
|
1126
1119
|
pattern: z.string().min(1).optional().describe("Case-insensitive regex tested against each raw JSONL line. Omit to get a tail read of the most recent lines instead."),
|
|
1127
1120
|
tail: z.number().int().positive().optional().describe("Only used when `pattern` is omitted. How many of the most recent lines to return. Defaults to 20, capped at 200."),
|
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.128",
|
|
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",
|