@thehammer/danx-dashboard-mcp 0.1.23 → 0.1.24

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/handlers.js CHANGED
@@ -89,8 +89,8 @@ export async function issueCreate(client, args, defaultBoard) {
89
89
  body.effort_level = args.effort_level;
90
90
  if (args.list_id !== undefined)
91
91
  body.list_id = args.list_id;
92
- if (args.required_gates !== undefined)
93
- body.required_gates = args.required_gates;
92
+ if (args.gate_decisions !== undefined)
93
+ body.gate_decisions = args.gate_decisions;
94
94
  if (args.phase_children !== undefined)
95
95
  body.phase_children = args.phase_children;
96
96
  return client.request({ method: "POST", path: "", body, board });
@@ -346,7 +346,7 @@ export async function issueRequiresHuman(client, args) {
346
346
  * POST /api/issues/:id/quality-gates/:gate {required} — the same write the
347
347
  * dashboard drawer's Quality Gates tab performs (DX-1181). This is the ONLY
348
348
  * post-create path to mark a gate required/not — `issue_create` carries
349
- * `required_gates[]` at birth, and `issue_edit` rejects gate keys; without
349
+ * `gate_decisions` at birth, and `issue_edit` rejects gate keys; without
350
350
  * this tool an agent that created a card cannot turn a gate on afterward.
351
351
  *
352
352
  * `gate` is a registry name (`plan-dependency` | `plan-architecture` |
package/dist/index.js CHANGED
@@ -165,7 +165,7 @@ server.tool("issue_get", 'Fetch a single hydrated issue via GET /api/issues/:id.
165
165
  ...boardField,
166
166
  }, async (args) => jsonResult(await issueGet(client, args)));
167
167
  // ---------------- issue_create ----------------
168
- server.tool("issue_create", 'Create a fresh card via POST /api/issues. Board-scoped; defaults to the dispatch\'s board. Pass `board` (a qualified id `<repo>:<slug>`) to create the card on another board (forwarded into body.board + ?board=; unknown board → 404). INVARIANT: type=Epic REQUIRES non-empty phase_children[] (epic-with-phases atomicity per DX-575) and the route atomically inserts the epic + every phase in ONE transaction. Non-Epic types REFUSE phase_children[] with 400. Status defaults to Review (no lifecycle timestamps stamped on create). parent_id optional. ac items take {title}; phase children inherit the new epic\'s id as parent_id. Optional list_id PLACES the card directly into a column in ONE call. **Pass EITHER a board_lists id OR the list\'s display NAME (case-insensitive, emoji-tolerant — e.g. a queue name like "⚙️ Fulfillment Queue" or just "Fulfillment Queue") — the server resolves a name to its id.** The card lands DIRECTLY in that column with the matching lifecycle stamped automatically — a `ready`-type queue → ToDo, a `completed` list → Done, etc. **You do NOT need a separate issue_transition(ready) + issue_edit(list_id) afterward — just pass the queue name here and the card is created already in that column.** Omit list_id for the default (Review). NOT valid on type=Epic (Epic status derives from children) → 400. Unknown name/id → 400. Optional required_gates[] flags which quality gates (dependency, architecture, tdd, code) are REQUIRED on this card so they run pre-dispatch (only when the board also has that gate enabled); unknown gate name 400.', {
168
+ server.tool("issue_create", 'Create a fresh card via POST /api/issues. Board-scoped; defaults to the dispatch\'s board. Pass `board` (a qualified id `<repo>:<slug>`) to create the card on another board (forwarded into body.board + ?board=; unknown board → 404). INVARIANT: type=Epic REQUIRES non-empty phase_children[] (epic-with-phases atomicity per DX-575) and the route atomically inserts the epic + every phase in ONE transaction. Non-Epic types REFUSE phase_children[] with 400. Status defaults to Review (no lifecycle timestamps stamped on create). parent_id optional. ac items take {title}; phase children inherit the new epic\'s id as parent_id. Optional list_id PLACES the card directly into a column in ONE call. **Pass EITHER a board_lists id OR the list\'s display NAME (case-insensitive, emoji-tolerant — e.g. a queue name like "⚙️ Fulfillment Queue" or just "Fulfillment Queue") — the server resolves a name to its id.** The card lands DIRECTLY in that column with the matching lifecycle stamped automatically — a `ready`-type queue → ToDo, a `completed` list → Done, etc. **You do NOT need a separate issue_transition(ready) + issue_edit(list_id) afterward — just pass the queue name here and the card is created already in that column.** Omit list_id for the default (Review). NOT valid on type=Epic (Epic status derives from children) → 400. Unknown name/id → 400. **gate_decisions is REQUIRED whenever the board has any OPTIONAL quality gate for the card\'s type** (DX-1594): supply one `{gate, enabled, note}` per board-optional gate. The create FAILS CLOSED — a missing decision returns 400 `{error, required_gate_decisions:[...]}` enumerating exactly which gates to answer, so just retry with a decision for each listed gate. `required`/`disabled` board gates take no decision; a board with no optional gates needs no gate_decisions at all.', {
169
169
  type: z.enum(ISSUE_TYPES),
170
170
  title: z.string().min(1),
171
171
  description: z.string(),
@@ -173,10 +173,14 @@ server.tool("issue_create", 'Create a fresh card via POST /api/issues. Board-sco
173
173
  ac: z.array(z.object({ title: z.string().min(1) })).optional(),
174
174
  effort_level: z.enum(EFFORT_VALUES).nullable().optional(),
175
175
  list_id: z.string().min(1).nullable().optional(),
176
- required_gates: z
177
- .array(z.string().min(1))
176
+ gate_decisions: z
177
+ .array(z.object({
178
+ gate: z.string().min(1),
179
+ enabled: z.boolean(),
180
+ note: z.string(),
181
+ }))
178
182
  .optional()
179
- .describe('Optional array of quality-gate names to mark REQUIRED on this card (e.g. ["architecture","tdd"]). Board requirement is TRI-STATE per gate (`board_quality_gate_settings.default_state`), NOT binary: `required` runs always; `optional` runs WHEN flagged on the card (per-card opt-in `optional` is ENABLED, not off); `disabled` never runs (flag inert). So flagging makes the gate run unless the board state is `disabled`. Use this when you (the creating agent) judge the card needs that pre-dispatch review gate; omit for cards that don\'t. Known gates: dependency, architecture, tdd, code. The operator can also toggle these per-card later in the issue drawer.'),
183
+ .describe('REQUIRED fail-closed quality-gate decisions (DX-1594 replaces required_gates). One {gate, enabled, note} per board-OPTIONAL gate of the card\'s type: `enabled` answers whether the gate runs on this card, `note` records the rationale (persisted as the decision rationale, distinct from the reviewer verdict). The board requirement is TRI-STATE per gate (`board_quality_gate_settings.default_state`): `required` runs always (NO decision — auto-on); `optional` REQUIRES a decision here (unanswered the create 400s); `disabled` never runs (NO decision). Omit this only on a board with no optional gates; otherwise the 400 body\'s `required_gate_decisions` lists exactly which gates to answer retry with {enabled, note} for each. A decision naming a non-optional gate is rejected 400.'),
180
184
  phase_children: z
181
185
  .array(z.object({
182
186
  type: z.enum(NON_EPIC_TYPES),
@@ -184,6 +188,14 @@ server.tool("issue_create", 'Create a fresh card via POST /api/issues. Board-sco
184
188
  description: z.string(),
185
189
  ac: z.array(z.object({ title: z.string().min(1) })).optional(),
186
190
  effort_level: z.enum(EFFORT_VALUES).nullable().optional(),
191
+ gate_decisions: z
192
+ .array(z.object({
193
+ gate: z.string().min(1),
194
+ enabled: z.boolean(),
195
+ note: z.string(),
196
+ }))
197
+ .optional()
198
+ .describe("Per-child fail-closed gate decisions — same shape + rule as the root gate_decisions, resolved against THIS child's own type. Required when the child's type has board-optional gates."),
187
199
  }))
188
200
  .optional(),
189
201
  ...boardField,
@@ -295,7 +307,7 @@ server.tool("issue_requires_human", "Set or clear the requires_human dispatch ga
295
307
  ...boardField,
296
308
  }, async (args) => jsonResult(await issueRequiresHuman(client, args)));
297
309
  // ---------------- issue_quality_gate ----------------
298
- server.tool("issue_quality_gate", "Toggle a single card's per-card quality-gate `required` flag via POST /api/issues/:id/quality-gates/:gate {required} — the SAME write the dashboard drawer's Quality Gates tab performs (DX-1181). This is the ONLY post-create way to mark a gate required/not-required: `issue_create` carries `required_gates[]` at birth, and `issue_edit` REJECTS gate keys (400 offending_keys) — without this tool a card created without a gate can never have it turned on by an agent. `gate` is a registry name: `plan-dependency` | `plan-architecture` | `plan-tdd` | `code-test-quality` | `code-architecture` | `code-quality` (the PRE/plan- gates run before the work dispatch; the POST/code- gates block issue_transition complete). Unknown gate → 400 (never a silent no-op); a card with no seeded row for a registered gate → 500 (canonical corruption). NOTE board requirement is TRI-STATE per gate (`board_quality_gate_settings.default_state`, the Agents-tab surface), NOT a binary on/off: `required` = gate always runs (this flag irrelevant); `optional` = gate runs WHEN this per-card flag is true (per-card opt-in — `optional` is ENABLED, NOT off); `disabled` = never runs (this flag inert). So flipping `required:true` here LAUNCHES the gate when the board state is `required` OR `optional`; it is inert ONLY when the board state is `disabled`. Do not read `optional` as off. (Source of truth: `isGateEffectivelyRequired` in `src/issues/quality-gates/read.ts`.) Returns the hydrated issue. Board-scoped; pass `board` (`<repo>:<slug>`) to target another board.", {
310
+ server.tool("issue_quality_gate", "Toggle a single card's per-card quality-gate `required` flag via POST /api/issues/:id/quality-gates/:gate {required} — the SAME write the dashboard drawer's Quality Gates tab performs (DX-1181). This is the ONLY post-create way to mark a gate required/not-required: `issue_create` carries `gate_decisions` at birth, and `issue_edit` REJECTS gate keys (400 offending_keys) — without this tool a card created without a gate can never have it turned on by an agent. `gate` is a registry name: `plan-dependency` | `plan-architecture` | `plan-tdd` | `code-test-quality` | `code-architecture` | `code-quality` (the PRE/plan- gates run before the work dispatch; the POST/code- gates block issue_transition complete). Unknown gate → 400 (never a silent no-op); a card with no seeded row for a registered gate → 500 (canonical corruption). NOTE board requirement is TRI-STATE per gate (`board_quality_gate_settings.default_state`, the Agents-tab surface), NOT a binary on/off: `required` = gate always runs (this flag irrelevant); `optional` = gate runs WHEN this per-card flag is true (per-card opt-in — `optional` is ENABLED, NOT off); `disabled` = never runs (this flag inert). So flipping `required:true` here LAUNCHES the gate when the board state is `required` OR `optional`; it is inert ONLY when the board state is `disabled`. Do not read `optional` as off. (Source of truth: `isGateEffectivelyRequired` in `src/issues/quality-gates/read.ts`.) Returns the hydrated issue. Board-scoped; pass `board` (`<repo>:<slug>`) to target another board.", {
299
311
  id: z.string().min(1),
300
312
  gate: z.enum([
301
313
  "plan-dependency",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thehammer/danx-dashboard-mcp",
3
- "version": "0.1.23",
3
+ "version": "0.1.24",
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",