@thehammer/danx-dashboard-mcp 0.1.131 → 0.1.134

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
@@ -23,7 +23,6 @@
23
23
  */
24
24
  import { readFile } from "node:fs/promises";
25
25
  import { basename, extname, isAbsolute } from "node:path";
26
- import { oneLine } from "./one-line.js";
27
26
  import { resolvePriority } from "./priority.js";
28
27
  /**
29
28
  * `filter`/`fields`/`sort` are JSON/CSV-encoded onto the query string (the
@@ -297,82 +296,6 @@ export async function issueTransition(client, args) {
297
296
  board,
298
297
  });
299
298
  }
300
- /** How much of a problem statement the reminder quotes — enough to recognise it. */
301
- const REMINDER_STATEMENT_MAX = 120;
302
- function isReminderProblem(value) {
303
- const p = value;
304
- return (typeof p === "object" &&
305
- p !== null &&
306
- typeof p.id === "number" &&
307
- typeof p.statement === "string" &&
308
- typeof p.open === "boolean" &&
309
- Array.isArray(p.solutions));
310
- }
311
- /**
312
- * Attach the problems reminder to a SUCCESSFUL `issue_problem add`.
313
- *
314
- * DX-2830 — an added problem IS the moment a card is put in front of a human;
315
- * there is no separate gate to set or refuse. Whether each open problem lists
316
- * its viable solutions is feedback, never refusal: a problem with zero
317
- * solutions is valid (the operator answers free-form), and refusing it would
318
- * push the agent into inventing options. So the reminder rides the success
319
- * response and names the STILL-OPEN problems only — an answered sibling needs
320
- * nothing more.
321
- *
322
- * The write's own envelope is returned untouched beside it. A refused write
323
- * gets no reminder and no extra request. A failure READING the problems is
324
- * reported inside the reminder rather than thrown: the add already succeeded,
325
- * and throwing would tell the agent it failed when it did not.
326
- */
327
- export async function withProblemsReminder(client, id, board, result) {
328
- if (!result.ok)
329
- return result;
330
- let listed;
331
- try {
332
- listed = await client.request({
333
- method: "GET",
334
- path: `/${encodeURIComponent(id)}/problems`,
335
- board,
336
- });
337
- }
338
- catch (err) {
339
- return { ...result, problems_reminder: unreadableReminder(id, err instanceof Error ? err.message : String(err)) };
340
- }
341
- // `body` is the server's JSON verbatim — `null` is valid JSON, so read it null-safely;
342
- // a throw here would land OUTSIDE the try above and misreport the successful write.
343
- const problems = listed.ok ? listed.body?.problems : undefined;
344
- if (!Array.isArray(problems) || !problems.every(isReminderProblem)) {
345
- // A 2xx whose body is not the problem list is a contract break, not a refusal —
346
- // say so, rather than leaving a bare "HTTP 200" that reads like success.
347
- const detail = listed.ok ? `HTTP ${listed.status}, unexpected shape` : `HTTP ${listed.status}`;
348
- return { ...result, problems_reminder: unreadableReminder(id, detail) };
349
- }
350
- const open = problems.filter((p) => p.open);
351
- return { ...result, problems_reminder: { open_problem_count: open.length, instruction: openProblemsInstruction(id, open) } };
352
- }
353
- function openProblemsInstruction(id, open) {
354
- if (open.length === 0) {
355
- // Only reachable when every problem was answered between the add and this
356
- // read — the card no longer needs a human.
357
- return `No problem on ${id} is open any more — each was answered already. Re-read the card before you stop.`;
358
- }
359
- const listing = open
360
- .map((p) => {
361
- const statement = oneLine(p.statement, REMINDER_STATEMENT_MAX);
362
- const count = p.solutions.length;
363
- return `problem ${p.id} "${statement}" — ${count === 0 ? "NO SOLUTIONS LISTED" : `${count} solution${count === 1 ? "" : "s"}`}`;
364
- })
365
- .join("; ");
366
- return (`${open.length} open problem${open.length === 1 ? "" : "s"} on ${id}: ${listing}. ` +
367
- `Before you stop, list EVERY viable solution to each with issue_solution({id: "${id}", problem_id, action: "add", title, body, pro, con}) and mark the one you recommend with recommended: true, ` +
368
- `and make every other question the operator must answer its own problem with issue_problem. The card needs a human until every open problem is answered.`);
369
- }
370
- function unreadableReminder(id, detail) {
371
- return {
372
- open_problem_count: null,
373
- instruction: `Could not read the problems on ${id} (${detail}). Check with issue_problem({id: "${id}", action: "list"}) and make sure EVERY viable solution to each open problem is listed before you stop.`,
374
- };
375
- }
376
299
  export async function issueTriage(client, args) {
377
300
  const { id, board, ...body } = args;
378
301
  return client.request({
@@ -637,8 +560,13 @@ export async function issueProblem(client, args) {
637
560
  body.summary = args.summary;
638
561
  if (args.solutions !== undefined)
639
562
  body.solutions = args.solutions;
640
- const result = await client.request({ method: "POST", path: `/${idEnc}/problems`, body, board });
641
- return withProblemsReminder(client, args.id, board, result);
563
+ // DX-3365 — the server's own response now carries the reminder (a
564
+ // `reminders: [{key, text}]` field appended by the registry
565
+ // middleware, `src/issues/reminders/middleware.ts`); this used to be an
566
+ // EXTRA client-side GET (`withProblemsReminder`, deleted) computing the
567
+ // identical text a request round-trip later. Passed through verbatim —
568
+ // no dual path.
569
+ return client.request({ method: "POST", path: `/${idEnc}/problems`, body, board });
642
570
  }
643
571
  case "edit": {
644
572
  const problemId = need.id(args.problem_id, "problem_id");
package/dist/index.js CHANGED
@@ -653,7 +653,7 @@ const STEP_INPUT = z.lazy(() => z
653
653
  steps: z.array(STEP_INPUT).optional().describe("this step's own children, nested — refused past 3 levels total"),
654
654
  })
655
655
  .strict());
656
- strictTool("issue_problem", "A card's PROBLEMS via /api/issues/:id/problems[/:pid]: one statement the operator must resolve (a question, or a flaw in the plan) OR an action only a person can carry out, each with its own solutions/answers — one problem per question. OPEN = not yet answered; the card needs a human exactly while open_problem_count > 0 — there is no separate flag to set, adding a problem IS putting the card in front of a human. list → live problems in order, each {id, statement, context, type, summary, content_hash, open, solutions[], decisions[]}; add {statement, context?, type?, summary?, solutions?} → problem_id + solution_ids in one transaction (zero solutions is valid: the operator answers free-form) plus `problems_reminder: {open_problem_count, instruction}` naming each open problem's solution count; edit :pid {base_hash, statement, context?, summary?}; remove :pid {base_hash} — always allowed, even as the card's last open problem (removing it just means the card no longer needs a human). A stale base_hash → 409 `stale_problem` with currentHash + currentProblem: merge, then retry. No answer action — the operator answers in the dashboard. `statement` is capped at 200 characters (a 400 names the actual length otherwise): write it as ONE plain sentence (a question for a question, the deed itself for an action), and put any investigation detail in `context` (markdown, no cap) instead of running it on. Good: statement \"Which cache should we use?\", context \"Redis fits the read-heavy path; see benchmark in #123. Memcached is simpler ops but no persistence.\" Bad: statement \"We looked at Redis vs Memcached, ran benchmarks showing Redis 3x faster on reads, but Memcached has simpler ops and we're not sure persistence matters here since the cache is fully rebuildable from Postgres...\" (too long, refused — move it to context). Every problem is also a `type`, with a `summary` distinct from `context`: read BOTH fields' own descriptions below before your first `add` — together they teach which type this is, and the three-field split (`statement` / `summary` / `context`) an action actually needs.", {
656
+ 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 a `reminders: [{key, text}]` array (DX-3365 — every MCP-response reminder rides this ONE field now, DB-registry-driven and operator-overridable from the dashboard) naming each still-open problem's solution count; edit :pid {base_hash, statement, context?, summary?}; remove :pid {base_hash} — always allowed, even as the card's last open problem (removing it just means the card no longer needs a human). A stale base_hash → 409 `stale_problem` with currentHash + currentProblem: merge, then retry. No answer action — the operator answers in the dashboard. `statement` is capped at 200 characters (a 400 names the actual length otherwise): write it as ONE plain sentence (a question for a question, the deed itself for an action), and put any investigation detail in `context` (markdown, no cap) instead of running it on. Good: statement \"Which cache should we use?\", context \"Redis fits the read-heavy path; see benchmark in #123. Memcached is simpler ops but no persistence.\" Bad: statement \"We looked at Redis vs Memcached, ran benchmarks showing Redis 3x faster on reads, but Memcached has simpler ops and we're not sure persistence matters here since the cache is fully rebuildable from Postgres...\" (too long, refused — move it to context). Every problem is also a `type`, with a `summary` distinct from `context`: read BOTH fields' own descriptions below before your first `add` — together they teach which type this is, and the three-field split (`statement` / `summary` / `context`) an action actually needs.", {
657
657
  id: z.string().min(1),
658
658
  action: z.enum(["list", "add", "edit", "remove"]),
659
659
  problem_id: z.number().int().positive().optional().describe("edit/remove"),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thehammer/danx-dashboard-mcp",
3
- "version": "0.1.131",
3
+ "version": "0.1.134",
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",