@thehammer/danx-dashboard-mcp 0.1.129 → 0.1.132
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 +59 -61
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -343,14 +343,19 @@ const boardField = {
|
|
|
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 (e.g. "Guest checkout rejects
|
|
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
|
|
348
|
-
const DESCRIPTION_DESCRIBE = 'The full body ("Context"): evidence, examples, technical detail; markdown, collapsed by default. Operator questions go in issue_problem
|
|
346
|
+
const TITLE_DESCRIBE = 'Short, specific label naming the domain (e.g. "Guest checkout rejects gift-card carts"). 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.";
|
|
348
|
+
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.';
|
|
349
|
+
// DX-3335 — condensed from the now-deleted danxbot:comment-style skill (PLN-11
|
|
350
|
+
// R-16: fold a skill's rule into the tool description it governs rather than
|
|
351
|
+
// require a separate load). Shared by every prose-writing field below so an
|
|
352
|
+
// agent sees the same rules wherever it writes card markdown.
|
|
353
|
+
const MARKDOWN_STYLE_DESCRIBE = "Renders as markdown here. Use `##`/`###` headers, fenced code blocks with a language tag, inline code for paths/symbols/flags, `-`/`1.` lists, and GFM tables (`|---|`) for 2D data — never ASCII tables. Never escape markdown characters (`\\*`, `\\_`) to \"play it safe\"; they render wrong. Don't repeat the same content as both prose and a list. Structure the content itself per `base:convey` (concept-first headline, diff, caveats, verify) — this rule only governs the markdown syntax.";
|
|
349
354
|
// ---------------- issue_list ----------------
|
|
350
355
|
strictTool("issue_list",
|
|
351
356
|
// DX-2735: trimmed to pay for the problem tools inside the work-profile
|
|
352
357
|
// injected-surface budget — same facts, no repeated prose.
|
|
353
|
-
"List cards via GET /api/issues. Board-scoped; see `board`. `filter`: type, parent_id, dispatchable_derived, status_derived[], self_dispatchable_derived, assigned_agent, include_closed, include_deleted, q (free text over id+title+description). THE DEFAULT ROW IS MINIMAL — scalars only (id, type, title, status, parent_id, priority, created_at, updated_at, assigned_agent, content_hash), no joins; opt into heavy data with `fields` groups: description (+ summary), problems (open_problem_count — a card needs a human exactly when this is > 0), ac, comments, retro, dependencies, triage, assignment, quality_gates, children, effort. `sort`: [{column, order}] over id|priority|repo_name|title|type|status_derived|triage_ice_total|created_at|updated_at; default priority desc (highest
|
|
358
|
+
"List cards via GET /api/issues. Board-scoped; see `board`. `filter`: type, parent_id, dispatchable_derived, status_derived[], self_dispatchable_derived, assigned_agent, include_closed, include_deleted, q (free text over id+title+description). THE DEFAULT ROW IS MINIMAL — scalars only (id, type, title, status, parent_id, priority, created_at, updated_at, assigned_agent, content_hash), no joins; opt into heavy data with `fields` groups: description (+ summary), problems (open_problem_count — a card needs a human exactly when this is > 0), ac, comments, retro, dependencies, triage, assignment, quality_gates, children, effort. `sort`: [{column, order}] over id|priority|repo_name|title|type|status_derived|triage_ice_total|created_at|updated_at; default priority desc (highest=most urgent first), repo_name asc, numeric-id tiebreaker. `limit`/`offset` page (uncapped by default). DX-3113 — `include_closed` defaults to FALSE: a bare call silently excludes every Done/Cancelled card (leaf AND container alike). The response always carries `total` (the full count matching every filter except limit/offset — compare against `issues.length` to tell an exhausted list from a truncated one) and, whenever `include_closed` was not explicitly `true`, `closed_excluded` (how many additional terminal cards the default withheld — re-call with `include_closed: true` to see them). issue_get reads one card in full.", {
|
|
354
359
|
filter: z
|
|
355
360
|
.object({
|
|
356
361
|
q: z.string().optional(),
|
|
@@ -384,7 +389,7 @@ strictTool("issue_list",
|
|
|
384
389
|
strictTool("issue_get",
|
|
385
390
|
// DX-2735: trimmed to pay for the problem tools inside the work-profile
|
|
386
391
|
// injected-surface budget — same facts, no repeated prose.
|
|
387
|
-
"Fetch one card (GET /api/issues/:id, `id`) or many (GET /api/issues/batch, `ids`, at most " + ISSUE_BATCH_GET_MAX + " — split larger sets); pass exactly one. Ids are globally unique, so both resolve from any board; the batch form is global and throws with `board`. THE DEFAULT ROW IS MINIMAL — scalars only (id, type, title, status, parent_id, priority, created_at, updated_at, assigned_agent, content_hash: the concurrency token issue_edit needs for title/description/checklists). `fields` opts into groups, per card in a batch too: description (body + summary), problems (live problems in order, each {id, statement, content_hash, open} with its solutions[] and decisions[] — a card needs a human exactly when open_problem_count > 0), ac (acceptance criteria + checklists), comments (see `comments_limit`/`comments_offset` below), retro, dependencies (waiting_on/conflict_on/blocked), triage (history + ICE), assignment (dispatch, assigned_agent, lifecycle timestamps), quality_gates (one row per gate {gate, required, status pending|pass|fail, completed_at, message}; a required PRE gate not `pass` pre-empts the work dispatch, and complete refuses while a required POST gate is not `pass`), children (ids + rollups), mirrors (external sync state), code_review_items, plans (every plan this card is on, `{id, ref, name}[]` via `plan_cards` — `ref` is the plan's `PLN-<id>`). The `comments` group
|
|
392
|
+
"Fetch one card (GET /api/issues/:id, `id`) or many (GET /api/issues/batch, `ids`, at most " + ISSUE_BATCH_GET_MAX + " — split larger sets); pass exactly one. Ids are globally unique, so both resolve from any board; the batch form is global and throws with `board`. THE DEFAULT ROW IS MINIMAL — scalars only (id, type, title, status, parent_id, priority, created_at, updated_at, assigned_agent, content_hash: the concurrency token issue_edit needs for title/description/checklists). `fields` opts into groups, per card in a batch too: description (body + summary), problems (live problems in order, each {id, statement, content_hash, open} with its solutions[] and decisions[] — a card needs a human exactly when open_problem_count > 0), ac (acceptance criteria + checklists), comments (see `comments_limit`/`comments_offset` below), retro, dependencies (waiting_on/conflict_on/blocked), triage (history + ICE), assignment (dispatch, assigned_agent, lifecycle timestamps), quality_gates (one row per gate {gate, required, status pending|pass|fail, completed_at, message}; a required PRE gate not `pass` pre-empts the work dispatch, and complete refuses while a required POST gate is not `pass`), children (ids + rollups), mirrors (external sync state), code_review_items, plans (every plan this card is on, `{id, ref, name}[]` via `plan_cards` — `ref` is the plan's `PLN-<id>`). The `comments` group PAGES ANCHORED AT THE NEWEST COMMENT: `comments_offset` counts back from it (offset 0 = the most recent comments_limit comments); `comments_total` is the real total, so `comments_offset + comments.length < comments_total` means older comments remain — page forward to reach them; a long-running card's decisive history often lives past the first page. WITHIN a page, comments stay CHRONOLOGICAL (oldest first) — same order as every other comment read; only the requested WINDOW is anchored at the newest end. `comments_limit`/`comments_offset` apply to the SINGLE-id form only (naming either alongside `ids` is refused). Single form: unknown id → 404. Batch form: `{issues: [...], not_found: [...ids]}` — an unknown or deleted id never fails the call.", {
|
|
388
393
|
id: z.string().min(1).optional(),
|
|
389
394
|
ids: z.array(z.string().min(1)).min(1).max(ISSUE_BATCH_GET_MAX).optional(),
|
|
390
395
|
fields: z
|
|
@@ -410,8 +415,8 @@ strictTool("issue_get",
|
|
|
410
415
|
...boardField,
|
|
411
416
|
}, async (args) => jsonResult(await issueGet(client, args)));
|
|
412
417
|
// ---------------- issue_create ----------------
|
|
413
|
-
strictTool("issue_create", '`plan`
|
|
414
|
-
'Create a card via POST /api/issues. Board-scoped; see `board`. type=Epic REQUIRES non-empty phase_children[] (epic
|
|
418
|
+
strictTool("issue_create", '`plan` 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. ' +
|
|
419
|
+
'Create a card via POST /api/issues. Board-scoped; see `board`. type=Epic REQUIRES non-empty phase_children[] (epic + 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, no follow-up transition needed. 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 — `{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
420
|
type: z.enum(ISSUE_TYPES),
|
|
416
421
|
title: z.string().min(1).describe(TITLE_DESCRIBE),
|
|
417
422
|
summary: z.string().min(1).optional().describe(SUMMARY_DESCRIBE),
|
|
@@ -490,7 +495,7 @@ strictTool("issue_create", '`plan` is REQUIRED on every create (DX-3006, no defa
|
|
|
490
495
|
...boardField,
|
|
491
496
|
}, async (args) => jsonResult(await issueCreate(client, args, config.board)));
|
|
492
497
|
// ---------------- 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. `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
|
|
498
|
+
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`; 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) — the card\'s optimistic-concurrency token — is 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` with `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
499
|
id: z.string().min(1),
|
|
495
500
|
title: z.string().min(1).optional().describe(TITLE_DESCRIBE),
|
|
496
501
|
summary: z
|
|
@@ -584,7 +589,7 @@ strictTool("issue_comment", "Comment CRUD via /api/issues/:id/comments[/:cid]. a
|
|
|
584
589
|
id: z.string().min(1),
|
|
585
590
|
action: z.enum(["add", "edit", "delete"]),
|
|
586
591
|
comment_id: z.number().int().positive().optional(),
|
|
587
|
-
text: z.string().min(1).optional(),
|
|
592
|
+
text: z.string().min(1).optional().describe(MARKDOWN_STYLE_DESCRIBE),
|
|
588
593
|
metadata: z.record(z.unknown()).optional(),
|
|
589
594
|
problem_id: z.number().int().positive().optional().describe("action=add only — thread this comment as a follow-up under a LIVE problem of this same card, without answering it"),
|
|
590
595
|
...boardField,
|
|
@@ -633,22 +638,20 @@ const STEP_INPUT = z.lazy(() => z
|
|
|
633
638
|
.int()
|
|
634
639
|
.positive()
|
|
635
640
|
.optional()
|
|
636
|
-
.describe("present ->
|
|
637
|
-
"
|
|
638
|
-
"
|
|
639
|
-
"
|
|
640
|
-
"
|
|
641
|
-
"(checked progress, a later single-step edit), or when two siblings under the same parent share a title."),
|
|
641
|
+
.describe("present -> matches an existing step by this id (400 if not a live child of this exact parent scope, " +
|
|
642
|
+
"never claimed by the fallback below); absent -> matches the first unclaimed same-titled sibling " +
|
|
643
|
+
"under the same parent, else creates new (title-based, never positional — keeps later id-less " +
|
|
644
|
+
"siblings' ids intact on a middle insert). Send ids anyway when identity matters (checked progress, " +
|
|
645
|
+
"a later edit) or siblings share a title."),
|
|
642
646
|
title: z
|
|
643
647
|
.string()
|
|
644
648
|
.min(1)
|
|
645
|
-
.describe("one imperative line
|
|
646
|
-
"in the title itself."),
|
|
649
|
+
.describe("one imperative line — labels (\"1.\", \"2a\") are DERIVED on read, never put one in the title."),
|
|
647
650
|
description: z.string().nullable().optional().describe("markdown detail, or null/omitted for none"),
|
|
648
651
|
steps: z.array(STEP_INPUT).optional().describe("this step's own children, nested — refused past 3 levels total"),
|
|
649
652
|
})
|
|
650
653
|
.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 —
|
|
654
|
+
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 — no separate flag, 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). Stale base_hash → 409 `stale_problem` with currentHash + currentProblem: merge, then retry. No answer action — the operator answers in the dashboard. `statement` capped at 200 characters (400 names the actual length otherwise): ONE plain sentence, with investigation detail in `context` (markdown, no cap) instead — e.g. statement \"Which cache should we use?\", context \"Redis fits the read-heavy path; see benchmark in #123.\" not a run-on statement. Every problem also has a `type` with a `summary` distinct from `context` — read both fields' descriptions before your first `add`.", {
|
|
652
655
|
id: z.string().min(1),
|
|
653
656
|
action: z.enum(["list", "add", "edit", "remove"]),
|
|
654
657
|
problem_id: z.number().int().positive().optional().describe("edit/remove"),
|
|
@@ -669,16 +672,15 @@ strictTool("issue_problem", "A card's PROBLEMS via /api/issues/:id/problems[/:pi
|
|
|
669
672
|
type: z
|
|
670
673
|
.enum(["question", "action"])
|
|
671
674
|
.optional()
|
|
672
|
-
.describe("add only. Test: could I do this myself if I tried harder, missing only a decision?
|
|
673
|
-
"statement is the question (one plain sentence); summary optional
|
|
674
|
-
"
|
|
675
|
-
"
|
|
676
|
-
"
|
|
677
|
-
"
|
|
678
|
-
"
|
|
679
|
-
"
|
|
680
|
-
"
|
|
681
|
-
"— decide deliberately every time."),
|
|
675
|
+
.describe("add only. Test: could I do this myself if I tried harder, missing only a decision? Yes → \"question\": " +
|
|
676
|
+
"statement is the question (one plain sentence); summary optional; context is the evidence; solutions[] " +
|
|
677
|
+
"are candidate ANSWERS with pro/con, done once the operator picks one. Blocker is access, credentials, " +
|
|
678
|
+
"hardware, a human's authority, or a system you genuinely cannot reach → \"action\": statement is WHAT " +
|
|
679
|
+
"IS TO BE DONE — the deed, never a question (\"Rotate the staging DB credential\", not \"Should we " +
|
|
680
|
+
"rotate it?\"); summary REQUIRED — WHY NEEDED AND WHY YOU CANNOT DO IT YOURSELF (no summary → 400, " +
|
|
681
|
+
"proves this isn't laziness); context is detail for the person carrying it out; solutions[] are the " +
|
|
682
|
+
"possible ROUTES, EACH MUST CARRY AT LEAST ONE STEP (400 otherwise). Omitted defaults to \"question\" " +
|
|
683
|
+
"— decide deliberately."),
|
|
682
684
|
summary: z
|
|
683
685
|
.string()
|
|
684
686
|
.nullable()
|
|
@@ -696,36 +698,32 @@ strictTool("issue_problem", "A card's PROBLEMS via /api/issues/:id/problems[/:pi
|
|
|
696
698
|
...boardField,
|
|
697
699
|
}, async (args) => jsonResult(await issueProblem(client, args)));
|
|
698
700
|
// ---------------- issue_solution ----------------
|
|
699
|
-
strictTool("issue_solution", "One problem's options via /api/issues/:id/problems/:pid/solutions[/:sid],
|
|
700
|
-
"procedure steps via .../solutions/:sid/steps[/:stepId]; `problem_id`
|
|
701
|
+
strictTool("issue_solution", "One problem's options via /api/issues/:id/problems/:pid/solutions[/:sid], plus (DX-3310) a solution's own " +
|
|
702
|
+
"procedure steps via .../solutions/:sid/steps[/:stepId]; `problem_id` REQUIRED (from issue_problem list/add; " +
|
|
701
703
|
"another problem's solution id → 404). add {title, body?, pro?, con?, recommended?, steps?}: title names the " +
|
|
702
|
-
"option
|
|
703
|
-
"
|
|
704
|
-
"
|
|
705
|
-
"(
|
|
706
|
-
"
|
|
707
|
-
"
|
|
708
|
-
"
|
|
709
|
-
"
|
|
710
|
-
"
|
|
711
|
-
"
|
|
712
|
-
"
|
|
713
|
-
"
|
|
714
|
-
"
|
|
715
|
-
"
|
|
716
|
-
"
|
|
717
|
-
"
|
|
718
|
-
"
|
|
719
|
-
"
|
|
720
|
-
"
|
|
721
|
-
"description
|
|
722
|
-
"
|
|
723
|
-
"live
|
|
724
|
-
"
|
|
725
|
-
"description?} — never moves a step (no parent_step_id/position here). remove_step {solution_id, step_id, " +
|
|
726
|
-
"base_hash} — soft-deletes it AND its own live children; refused (409) if this solution belongs to an ACTION " +
|
|
727
|
-
"problem and removing it would leave zero live steps. A stale base_hash on a step → 409 `stale_step` with " +
|
|
728
|
-
"currentHash + currentStep (carrying its real derived `label`).", {
|
|
704
|
+
"option, body/pro/con its markdown detail and case for/against; `steps` authors the WHOLE procedure in one " +
|
|
705
|
+
"call — {title, description?, steps?}[], nestable to 3 levels (4th refused 400, naming the step + limit). " +
|
|
706
|
+
"ACTION problem: `steps` must be non-empty (400 otherwise); QUESTION: may be omitted/empty. Labels " +
|
|
707
|
+
"(\"1\", \"2a\", \"2a.i\") are DERIVED on read from position — never put one in a title. edit :sid {base_hash, " +
|
|
708
|
+
"...changed fields, steps?, steps_base_hash?}: `steps` omitted leaves the procedure untouched; an explicit " +
|
|
709
|
+
"array (incl. [], refused if it leaves an action's solution with zero steps) DIFFS against it — send an " +
|
|
710
|
+
"existing step's `id` to keep it (even while retitling/reordering), omit `id` for a new step; any existing " +
|
|
711
|
+
"step left out is removed. An id-less node matches the first unclaimed same-titled sibling under the same " +
|
|
712
|
+
"parent (never positional), so an id-less MIDDLE insert keeps later id-less siblings' ids — send ids anyway " +
|
|
713
|
+
"when identity matters. `steps_base_hash` REQUIRED whenever `steps` is sent — the solution's current " +
|
|
714
|
+
"`steps_hash`; stale → 409 `stale_steps` with the current tree. Never resend the whole tree to fix one word " +
|
|
715
|
+
"— use add_step/edit_step/remove_step below. remove :sid {base_hash}: stale → 409 `stale_solution` with " +
|
|
716
|
+
"currentHash + currentSolution, merge and retry. At most ONE live recommended per problem — a second → 409 " +
|
|
717
|
+
"naming `recommended_solution_id`. A CHOSEN option's words and WHOLE PROCEDURE are frozen (409 — add a new " +
|
|
718
|
+
"one instead); every steps route below refuses once a decision has chosen this solution.\n\n" +
|
|
719
|
+
"Granular single-step actions, no tree resend: add_step {solution_id, title, description?, parent_step_id?, " +
|
|
720
|
+
"position?} — title is one imperative line (labels derived, never typed); parent_step_id omitted/null = " +
|
|
721
|
+
"top-level; position 1-indexed among current live children, omitted = append, beyond sibling count → 400 " +
|
|
722
|
+
"(never clamped); nesting past depth 3 → 400. edit_step {solution_id, step_id, base_hash, title?, " +
|
|
723
|
+
"description?} — never moves a step. remove_step {solution_id, step_id, base_hash} — soft-deletes it and " +
|
|
724
|
+
"its live children; refused 409 if this solution belongs to an ACTION problem and removing it leaves zero " +
|
|
725
|
+
"live steps. Stale base_hash on a step → 409 `stale_step` with currentHash + currentStep (real derived " +
|
|
726
|
+
"`label`).", {
|
|
729
727
|
id: z.string().min(1),
|
|
730
728
|
action: z.enum(["add", "edit", "remove", "add_step", "edit_step", "remove_step"]),
|
|
731
729
|
problem_id: z.number().int().positive(),
|
|
@@ -809,10 +807,10 @@ strictTool("issue_quality_gate_verdict", "Stamp an operator MANUAL quality-gate
|
|
|
809
807
|
...boardField,
|
|
810
808
|
}, async (args) => jsonResult(await issueQualityGateVerdict(client, args)));
|
|
811
809
|
// ---------------- issue_retro ----------------
|
|
812
|
-
strictTool("issue_retro", "Replace the retro block via PUT /api/issues/:id/retro. Body: {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) — retro ships when work concludes. Replace semantics: good/bad upsert; action_item_ids[] + commits[] + tests[] soft-delete prior live rows and insert with fresh ordinals. action_item_ids[] entries MUST match <PREFIX>-N. commits[] entries take {sha, subject?}. tests[] (DX-1646) is REQUIRED (empty array allowed — the \"ran no tests\" case): one row per test GROUP that ran (a whole suite/class — name the group,
|
|
810
|
+
strictTool("issue_retro", "Replace the retro block via PUT /api/issues/:id/retro. Body: {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) — retro ships when work concludes. Replace semantics: good/bad upsert; action_item_ids[] + commits[] + tests[] soft-delete prior live rows and insert with fresh ordinals. action_item_ids[] entries MUST match <PREFIX>-N. commits[] entries take {sha, subject?}. tests[] (DX-1646) is REQUIRED (empty array allowed — the \"ran no tests\" case): one row per test GROUP that ran (a whole suite/class — name the group, never individual unit tests) or per individual e2e test (kind:'e2e', few + expensive so listed explicitly). Each row: {name, kind:'group'|'e2e', num_tests, num_passing_tests, duration_ms} required; num_assertions + num_passing_assertions NULLABLE (vitest has no assertion totals — pass null or omit). `correctable_danxbot_problem` (DX-2794) is REQUIRED on every write, like tests[] — ANSWER HONESTLY: did THIS dispatch hit a problem in danxbot's own code or configuration (not merely \"this card was hard\") that danxbot could change so it stops happening? true REQUIRES a non-empty `correctable_danxbot_problem_description` naming the problem; false REQUIRES the description be empty. A true+described retro is read by a deterministic, no-LLM check and starts exactly one automated repair (fixes the problem or files a ready card) — the ONLY reliable channel for a danxbot defect found mid-dispatch to get fixed, so do not default to false out of haste.", {
|
|
813
811
|
id: z.string().min(1),
|
|
814
|
-
good: z.string(),
|
|
815
|
-
bad: z.string(),
|
|
812
|
+
good: z.string().describe("Free-form markdown (prose or list, they render identically). Style: see issue_comment's `text` param."),
|
|
813
|
+
bad: z.string().describe("Free-form markdown (prose or list, they render identically). Style: see issue_comment's `text` param."),
|
|
816
814
|
correctable_danxbot_problem: z
|
|
817
815
|
.boolean()
|
|
818
816
|
.describe("Required every time, answered honestly — see tool description. Determines whether the description field below must be filled or must be empty."),
|
|
@@ -909,7 +907,7 @@ strictTool("plan_list", "List every plan via GET /api/plans (DX-2683), and learn
|
|
|
909
907
|
.optional()
|
|
910
908
|
.describe("Filter to one computed status: awaiting-session, planning, building, complete. Omit for every plan."),
|
|
911
909
|
}, async (args) => jsonResult(await planList(client, args)));
|
|
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
|
|
910
|
+
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 + bridge message, cursor-paged newest first via `events_limit` (1.." + PLAN_GET_EVENTS_MAX_LIMIT + ", default " + PLAN_GET_EVENTS_DEFAULT_LIMIT + ") and `events_before` (a previous page's `next_cursor`; omit for the newest page); filter with `events_kinds` (one+, see its enum), `events_origin` (one, see its enum), `events_writer` (exact name); every `events_*` param without `fields: [\"events\"]` is a 400, same as `cards_*` above; response carries `events: {items: [{id, at, kind, writer, origin, originSessionId, targetSessionId, cardId, cardTitle, boardId, detail}], next_cursor}` — `next_cursor` null on the last page; an event on an unreadable board is left out, plan-level events always visible). `session`/`sessionListenerAttached`/`available_field_groups` ride every response regardless. `sessionListenerAttached` is `false` for a few seconds right after `plan_connect` while the event bridge starts; still `false` after that while connected means plan events aren't 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`.", {
|
|
913
911
|
plan_id: z
|
|
914
912
|
.number()
|
|
915
913
|
.int()
|
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.132",
|
|
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",
|