@papi-ai/server 0.7.71 → 0.7.72
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/backfill-cycle-metrics.js +7 -0
- package/dist/index.js +325 -81
- package/package.json +1 -1
|
@@ -1621,6 +1621,13 @@ Check PAPI_PROJECT_ID in your .mcp.json config. Find your project ID in the PAPI
|
|
|
1621
1621
|
insertPlanRun(entry) {
|
|
1622
1622
|
return this.invoke("insertPlanRun", [entry]);
|
|
1623
1623
|
}
|
|
1624
|
+
/** task-2940: forwarded per-tool telemetry write. Requires the deployed
|
|
1625
|
+
* data-proxy to carry the matching `insertToolRun` case — until that deploy
|
|
1626
|
+
* lands, hosted callers fail this invoke and the caller swallows it (telemetry
|
|
1627
|
+
* must never fail a tool call). */
|
|
1628
|
+
insertToolRun(entry) {
|
|
1629
|
+
return this.invoke("insertToolRun", [entry]);
|
|
1630
|
+
}
|
|
1624
1631
|
getCostSummary(cycleNumber) {
|
|
1625
1632
|
return this.invoke("getCostSummary", [cycleNumber]);
|
|
1626
1633
|
}
|
package/dist/index.js
CHANGED
|
@@ -1738,6 +1738,13 @@ Check PAPI_PROJECT_ID in your .mcp.json config. Find your project ID in the PAPI
|
|
|
1738
1738
|
insertPlanRun(entry) {
|
|
1739
1739
|
return this.invoke("insertPlanRun", [entry]);
|
|
1740
1740
|
}
|
|
1741
|
+
/** task-2940: forwarded per-tool telemetry write. Requires the deployed
|
|
1742
|
+
* data-proxy to carry the matching `insertToolRun` case — until that deploy
|
|
1743
|
+
* lands, hosted callers fail this invoke and the caller swallows it (telemetry
|
|
1744
|
+
* must never fail a tool call). */
|
|
1745
|
+
insertToolRun(entry) {
|
|
1746
|
+
return this.invoke("insertToolRun", [entry]);
|
|
1747
|
+
}
|
|
1741
1748
|
getCostSummary(cycleNumber) {
|
|
1742
1749
|
return this.invoke("getCostSummary", [cycleNumber]);
|
|
1743
1750
|
}
|
|
@@ -16918,7 +16925,9 @@ async function viewBoard(adapter2, phaseFilter, options) {
|
|
|
16918
16925
|
total,
|
|
16919
16926
|
returned: paged.length,
|
|
16920
16927
|
offset,
|
|
16921
|
-
hasMore: offset + paged.length < total
|
|
16928
|
+
hasMore: offset + paged.length < total,
|
|
16929
|
+
totalUnfiltered: allTasks.length,
|
|
16930
|
+
limit
|
|
16922
16931
|
};
|
|
16923
16932
|
}
|
|
16924
16933
|
async function viewBoardSummary(adapter2) {
|
|
@@ -16966,7 +16975,87 @@ async function archiveTasks(adapter2, phases, statuses) {
|
|
|
16966
16975
|
return { archivedCount: result.archivedCount, phases };
|
|
16967
16976
|
}
|
|
16968
16977
|
|
|
16978
|
+
// src/lib/projection.ts
|
|
16979
|
+
function resolveFields(raw, allowed, toolName) {
|
|
16980
|
+
if (raw === void 0 || raw === null) return { ok: true, fields: null };
|
|
16981
|
+
if (typeof raw !== "string") {
|
|
16982
|
+
return { ok: false, error: `${toolName}: fields must be a comma-separated string (got ${typeof raw}).` };
|
|
16983
|
+
}
|
|
16984
|
+
const trimmed = raw.trim();
|
|
16985
|
+
if (trimmed === "") return { ok: true, fields: null };
|
|
16986
|
+
const canonical = new Map(allowed.map((a) => [a.toLowerCase(), a]));
|
|
16987
|
+
const requested = [];
|
|
16988
|
+
for (const part of trimmed.split(",")) {
|
|
16989
|
+
const token = part.trim();
|
|
16990
|
+
if (token === "") continue;
|
|
16991
|
+
const hit = canonical.get(token.toLowerCase());
|
|
16992
|
+
if (!hit) {
|
|
16993
|
+
return {
|
|
16994
|
+
ok: false,
|
|
16995
|
+
error: `${toolName}: unknown field "${token}". Allowed fields: ${allowed.join(", ")}.`
|
|
16996
|
+
};
|
|
16997
|
+
}
|
|
16998
|
+
if (!requested.includes(hit)) requested.push(hit);
|
|
16999
|
+
}
|
|
17000
|
+
if (requested.length === 0) return { ok: true, fields: null };
|
|
17001
|
+
if (!requested.includes("id")) requested.unshift("id");
|
|
17002
|
+
return { ok: true, fields: requested };
|
|
17003
|
+
}
|
|
17004
|
+
function selectColumns(columns, fields) {
|
|
17005
|
+
if (!fields) return columns;
|
|
17006
|
+
const byName = new Map(columns.map((c) => [c.name, c]));
|
|
17007
|
+
return fields.map((f) => byName.get(f)).filter((c) => c !== void 0);
|
|
17008
|
+
}
|
|
17009
|
+
function wantsMeta(raw) {
|
|
17010
|
+
return raw === true || raw === "true";
|
|
17011
|
+
}
|
|
17012
|
+
function formatMeta(meta) {
|
|
17013
|
+
return `**meta:** ${JSON.stringify(meta)}`;
|
|
17014
|
+
}
|
|
17015
|
+
function withMeta(body, meta) {
|
|
17016
|
+
return meta ? `${body}
|
|
17017
|
+
|
|
17018
|
+
${formatMeta(meta)}` : body;
|
|
17019
|
+
}
|
|
17020
|
+
function pad(value, width) {
|
|
17021
|
+
return value.length >= width ? value : value + " ".repeat(width - value.length);
|
|
17022
|
+
}
|
|
17023
|
+
function renderTable(headers, rows) {
|
|
17024
|
+
const widths = headers.map((h, i) => Math.max(h.length, ...rows.map((r) => r[i].length)));
|
|
17025
|
+
const headerLine = headers.map((h, i) => pad(h, widths[i])).join(" | ");
|
|
17026
|
+
const separator = widths.map((w) => "-".repeat(w)).join(" | ");
|
|
17027
|
+
const dataLines = rows.map((row) => row.map((cell, i) => pad(cell, widths[i])).join(" | "));
|
|
17028
|
+
return [headerLine, separator, ...dataLines].join("\n");
|
|
17029
|
+
}
|
|
17030
|
+
|
|
16969
17031
|
// src/tools/board.ts
|
|
17032
|
+
var BOARD_COLUMNS = [
|
|
17033
|
+
{ name: "priority", header: "Priority", value: (t) => t.priority },
|
|
17034
|
+
{ name: "id", header: "Task", value: (t) => t.id },
|
|
17035
|
+
{ name: "title", header: "Summary", value: (t) => truncateTitle(t.title) },
|
|
17036
|
+
{ name: "status", header: "Status", value: (t) => t.status },
|
|
17037
|
+
{ name: "cycle", header: "Cycle", value: (t) => t.cycle != null ? String(t.cycle) : "-" },
|
|
17038
|
+
{ name: "phase", header: "Phase", value: (t) => t.phase ?? "-" },
|
|
17039
|
+
{ name: "module", header: "Module", value: (t) => t.module ?? "-" },
|
|
17040
|
+
{ name: "epic", header: "Epic", value: (t) => t.epic ?? "-" },
|
|
17041
|
+
{ name: "complexity", header: "Effort", value: (t) => t.complexity ?? "-" },
|
|
17042
|
+
{ name: "createdAt", header: "Created", value: (t) => t.createdAt ?? "-" },
|
|
17043
|
+
{ name: "source", header: "Source", value: (t) => t.source ?? "-" }
|
|
17044
|
+
];
|
|
17045
|
+
var BOARD_FIELDS = BOARD_COLUMNS.map((c) => c.name);
|
|
17046
|
+
var BOARD_SINGLE_LABELS = {
|
|
17047
|
+
id: "Task",
|
|
17048
|
+
title: "Title",
|
|
17049
|
+
status: "Status",
|
|
17050
|
+
priority: "Priority",
|
|
17051
|
+
cycle: "Cycle",
|
|
17052
|
+
phase: "Phase",
|
|
17053
|
+
module: "Module",
|
|
17054
|
+
epic: "Epic",
|
|
17055
|
+
complexity: "Effort",
|
|
17056
|
+
createdAt: "Created",
|
|
17057
|
+
source: "Source"
|
|
17058
|
+
};
|
|
16970
17059
|
var boardViewTool = {
|
|
16971
17060
|
name: "board_view",
|
|
16972
17061
|
description: 'View the Board. To find a SPECIFIC task or subset, FILTER FIRST \u2014 do not dump the whole board: pass task_id for one task (full detail), query="<text>" for a title/notes substring match, or cycle=<n> for one cycle. Combine with status/phase. By default shows active tasks only (excludes Done/Cancelled), sorted by priority, limited to 50; titles are truncated in the table (single-task lookup shows the full title). Use status="all" to see everything, mode="summary" for counts only. Does not call the Anthropic API.',
|
|
@@ -17011,6 +17100,14 @@ var boardViewTool = {
|
|
|
17011
17100
|
type: "string",
|
|
17012
17101
|
enum: ["full", "summary"],
|
|
17013
17102
|
description: 'Output mode: "full" (default) shows task table, "summary" shows counts only.'
|
|
17103
|
+
},
|
|
17104
|
+
fields: {
|
|
17105
|
+
type: "string",
|
|
17106
|
+
description: `Optional sparse-field projection \u2014 comma-separated column names, e.g. "title,status". Returns ONLY those columns (id is always included so rows stay addressable) which is far cheaper on session context than the full table. Allowed: ${BOARD_FIELDS.join(", ")}. An unknown name is an error.`
|
|
17107
|
+
},
|
|
17108
|
+
meta: {
|
|
17109
|
+
type: "boolean",
|
|
17110
|
+
description: "Append a {total, filtered, returned, limit, offset} envelope so you can tell a truncated result from a complete one. Default false."
|
|
17014
17111
|
}
|
|
17015
17112
|
},
|
|
17016
17113
|
required: []
|
|
@@ -17175,14 +17272,27 @@ var boardEditTool = {
|
|
|
17175
17272
|
required: ["task_id"]
|
|
17176
17273
|
}
|
|
17177
17274
|
};
|
|
17178
|
-
function pad(value, width) {
|
|
17179
|
-
return value.length >= width ? value : value + " ".repeat(width - value.length);
|
|
17180
|
-
}
|
|
17181
17275
|
var TITLE_MAX = 80;
|
|
17182
17276
|
function truncateTitle(title) {
|
|
17183
17277
|
return title.length > TITLE_MAX ? `${title.slice(0, TITLE_MAX - 1)}\u2026` : title;
|
|
17184
17278
|
}
|
|
17185
|
-
function formatSingleTask(t) {
|
|
17279
|
+
function formatSingleTask(t, fields) {
|
|
17280
|
+
if (fields) {
|
|
17281
|
+
const values2 = {
|
|
17282
|
+
id: t.id,
|
|
17283
|
+
title: t.title,
|
|
17284
|
+
status: t.status,
|
|
17285
|
+
priority: t.priority,
|
|
17286
|
+
cycle: t.cycle != null ? String(t.cycle) : "-",
|
|
17287
|
+
phase: t.phase ?? "-",
|
|
17288
|
+
module: t.module ?? "-",
|
|
17289
|
+
epic: t.epic ?? "-",
|
|
17290
|
+
complexity: t.complexity ?? "-",
|
|
17291
|
+
createdAt: t.createdAt ?? "-",
|
|
17292
|
+
source: t.source ?? "-"
|
|
17293
|
+
};
|
|
17294
|
+
return fields.map((f) => `- **${BOARD_SINGLE_LABELS[f] ?? f}:** ${values2[f]}`).join("\n");
|
|
17295
|
+
}
|
|
17186
17296
|
const lines = [
|
|
17187
17297
|
`**${t.id} \u2014 ${t.title}**`,
|
|
17188
17298
|
"",
|
|
@@ -17199,41 +17309,20 @@ function formatSingleTask(t) {
|
|
|
17199
17309
|
if (t.why?.trim()) lines.push(`- **Why:** ${t.why.trim()}`);
|
|
17200
17310
|
return lines.join("\n");
|
|
17201
17311
|
}
|
|
17202
|
-
function formatBoard(result) {
|
|
17312
|
+
function formatBoard(result, fields) {
|
|
17203
17313
|
if (result.tasks.length === 0) {
|
|
17204
17314
|
return "No tasks found.";
|
|
17205
17315
|
}
|
|
17206
|
-
const
|
|
17207
|
-
const
|
|
17208
|
-
|
|
17209
|
-
t.id,
|
|
17210
|
-
truncateTitle(t.title),
|
|
17211
|
-
t.status,
|
|
17212
|
-
t.cycle != null ? String(t.cycle) : "-",
|
|
17213
|
-
t.phase ?? "-",
|
|
17214
|
-
t.module ?? "-",
|
|
17215
|
-
t.epic ?? "-",
|
|
17216
|
-
t.complexity ?? "-",
|
|
17217
|
-
t.createdAt ?? "-",
|
|
17218
|
-
t.source ?? "-"
|
|
17219
|
-
]);
|
|
17220
|
-
const widths = headers.map(
|
|
17221
|
-
(h, i) => Math.max(h.length, ...rows.map((r) => r[i].length))
|
|
17222
|
-
);
|
|
17223
|
-
const headerLine = headers.map((h, i) => pad(h, widths[i])).join(" | ");
|
|
17224
|
-
const separator = widths.map((w) => "-".repeat(w)).join(" | ");
|
|
17225
|
-
const dataLines = rows.map(
|
|
17226
|
-
(row) => row.map((cell, i) => pad(cell, widths[i])).join(" | ")
|
|
17227
|
-
);
|
|
17316
|
+
const columns = selectColumns(BOARD_COLUMNS, fields);
|
|
17317
|
+
const headers = columns.map((c) => c.header);
|
|
17318
|
+
const rows = result.tasks.map((t) => columns.map((c) => c.value(t)));
|
|
17228
17319
|
const lines = [];
|
|
17229
17320
|
lines.push(`**${result.returned} of ${result.total} tasks**`);
|
|
17230
17321
|
if (result.hasMore) {
|
|
17231
17322
|
lines.push(`_Showing ${result.offset + 1}\u2013${result.offset + result.returned}. Use offset=${result.offset + result.returned} to see more._`);
|
|
17232
17323
|
}
|
|
17233
17324
|
lines.push("");
|
|
17234
|
-
lines.push(
|
|
17235
|
-
lines.push(separator);
|
|
17236
|
-
lines.push(...dataLines);
|
|
17325
|
+
lines.push(renderTable(headers, rows));
|
|
17237
17326
|
return lines.join("\n");
|
|
17238
17327
|
}
|
|
17239
17328
|
function formatSummary(summary) {
|
|
@@ -17255,15 +17344,21 @@ function formatSummary(summary) {
|
|
|
17255
17344
|
}
|
|
17256
17345
|
async function handleBoardView(adapter2, args) {
|
|
17257
17346
|
const mode = args.mode;
|
|
17347
|
+
const projection = resolveFields(args.fields, BOARD_FIELDS, "board_view");
|
|
17348
|
+
if (!projection.ok) return errorResponse(projection.error);
|
|
17349
|
+
const fields = projection.fields;
|
|
17350
|
+
const emitMeta = wantsMeta(args.meta);
|
|
17258
17351
|
if (mode === "summary") {
|
|
17259
17352
|
const summary = await viewBoardSummary(adapter2);
|
|
17260
|
-
|
|
17353
|
+
const meta2 = emitMeta ? { total: summary.total, filtered: summary.total, returned: summary.total, limit: null, offset: 0 } : null;
|
|
17354
|
+
return textResponse(withMeta(formatSummary(summary), meta2));
|
|
17261
17355
|
}
|
|
17262
17356
|
const taskIdArg = args.task_id ?? args.display_id;
|
|
17263
17357
|
if (taskIdArg) {
|
|
17264
17358
|
const task = await adapter2.getTask(taskIdArg);
|
|
17265
17359
|
if (!task) return errorResponse(`Task ${taskIdArg} not found.`);
|
|
17266
|
-
|
|
17360
|
+
const meta2 = emitMeta ? { total: 1, filtered: 1, returned: 1, limit: null, offset: 0 } : null;
|
|
17361
|
+
return textResponse(withMeta(formatSingleTask(task, fields), meta2));
|
|
17267
17362
|
}
|
|
17268
17363
|
const result = await viewBoard(adapter2, void 0, {
|
|
17269
17364
|
phase: args.phase,
|
|
@@ -17273,17 +17368,26 @@ async function handleBoardView(adapter2, args) {
|
|
|
17273
17368
|
query: args.query,
|
|
17274
17369
|
cycle: args.cycle
|
|
17275
17370
|
});
|
|
17276
|
-
let output = formatBoard(result);
|
|
17277
|
-
|
|
17278
|
-
|
|
17279
|
-
|
|
17280
|
-
|
|
17281
|
-
|
|
17282
|
-
|
|
17371
|
+
let output = formatBoard(result, fields);
|
|
17372
|
+
if (!fields) {
|
|
17373
|
+
try {
|
|
17374
|
+
const comments = await adapter2.getRecentTaskComments?.(30);
|
|
17375
|
+
if (comments && comments.length > 0) {
|
|
17376
|
+
const taskIds = new Set(result.tasks.map((t) => t.id));
|
|
17377
|
+
const section = formatTaskComments(comments, taskIds, "**Task Comments:**");
|
|
17378
|
+
if (section) output += "\n" + section;
|
|
17379
|
+
}
|
|
17380
|
+
} catch {
|
|
17283
17381
|
}
|
|
17284
|
-
} catch {
|
|
17285
17382
|
}
|
|
17286
|
-
|
|
17383
|
+
const meta = emitMeta ? {
|
|
17384
|
+
total: result.totalUnfiltered,
|
|
17385
|
+
filtered: result.total,
|
|
17386
|
+
returned: result.returned,
|
|
17387
|
+
limit: result.limit,
|
|
17388
|
+
offset: result.offset
|
|
17389
|
+
} : null;
|
|
17390
|
+
return textResponse(withMeta(output, meta));
|
|
17287
17391
|
}
|
|
17288
17392
|
async function handleBoardDeprioritise(adapter2, args) {
|
|
17289
17393
|
const taskId = args.task_id;
|
|
@@ -21530,7 +21634,7 @@ async function listBuilds(adapter2, config2) {
|
|
|
21530
21634
|
]);
|
|
21531
21635
|
const currentCycle = health?.totalCycles ?? 0;
|
|
21532
21636
|
if (tasks.length === 0) {
|
|
21533
|
-
return { sorted: [], inProgress: [], backlog: [], blocked: [], warnings, isEmpty: true, noHandoffs: false, currentCycle };
|
|
21637
|
+
return { sorted: [], inProgress: [], backlog: [], blocked: [], warnings, isEmpty: true, noHandoffs: false, currentCycle, totalTasks: tasks.length };
|
|
21534
21638
|
}
|
|
21535
21639
|
const withHandoff = tasks.filter((t) => {
|
|
21536
21640
|
if (!t.buildHandoff) return false;
|
|
@@ -21541,7 +21645,7 @@ async function listBuilds(adapter2, config2) {
|
|
|
21541
21645
|
return true;
|
|
21542
21646
|
});
|
|
21543
21647
|
if (withHandoff.length === 0) {
|
|
21544
|
-
return { sorted: [], inProgress: [], backlog: [], blocked: [], warnings, isEmpty: false, noHandoffs: true, currentCycle };
|
|
21648
|
+
return { sorted: [], inProgress: [], backlog: [], blocked: [], warnings, isEmpty: false, noHandoffs: true, currentCycle, totalTasks: tasks.length };
|
|
21545
21649
|
}
|
|
21546
21650
|
const buildable = [];
|
|
21547
21651
|
const blocked = [];
|
|
@@ -21556,7 +21660,7 @@ async function listBuilds(adapter2, config2) {
|
|
|
21556
21660
|
const inProgress = buildable.filter((t) => t.status === "In Progress");
|
|
21557
21661
|
const backlog = buildable.filter((t) => t.status !== "In Progress");
|
|
21558
21662
|
const sorted = [...inProgress, ...backlog];
|
|
21559
|
-
return { sorted, inProgress, backlog, blocked, warnings, isEmpty: false, noHandoffs: sorted.length === 0 && blocked.length === 0, currentCycle };
|
|
21663
|
+
return { sorted, inProgress, backlog, blocked, warnings, isEmpty: false, noHandoffs: sorted.length === 0 && blocked.length === 0, currentCycle, totalTasks: tasks.length };
|
|
21560
21664
|
}
|
|
21561
21665
|
async function describeTask(adapter2, taskId) {
|
|
21562
21666
|
const task = await adapter2.getTask(taskId);
|
|
@@ -22976,6 +23080,17 @@ async function handleDocReorder(adapter2, args) {
|
|
|
22976
23080
|
|
|
22977
23081
|
// src/tools/build.ts
|
|
22978
23082
|
init_git();
|
|
23083
|
+
var BUILD_LIST_COLUMNS = [
|
|
23084
|
+
{ name: "id", header: "Task", value: (t) => t.id },
|
|
23085
|
+
{ name: "title", header: "Summary", value: (t) => t.title },
|
|
23086
|
+
{ name: "status", header: "Status", value: (t) => t.status },
|
|
23087
|
+
{ name: "priority", header: "Priority", value: (t) => t.priority },
|
|
23088
|
+
{ name: "complexity", header: "Effort", value: (t) => t.complexity ?? "-" },
|
|
23089
|
+
{ name: "module", header: "Module", value: (t) => t.module ?? "-" },
|
|
23090
|
+
{ name: "epic", header: "Epic", value: (t) => t.epic ?? "-" },
|
|
23091
|
+
{ name: "cycle", header: "Cycle", value: (t) => t.cycle != null ? String(t.cycle) : "-" }
|
|
23092
|
+
];
|
|
23093
|
+
var BUILD_LIST_FIELDS = BUILD_LIST_COLUMNS.map((c) => c.name);
|
|
22979
23094
|
var buildListTool = {
|
|
22980
23095
|
name: "build_list",
|
|
22981
23096
|
description: "List cycle tasks that have BUILD HANDOFFs ready for execution. Shows task ID, title, status, priority, and complexity. In Progress tasks appear first, then Backlog. Does not call the Anthropic API.",
|
|
@@ -22985,7 +23100,16 @@ var buildListTool = {
|
|
|
22985
23100
|
properties: {
|
|
22986
23101
|
limit: {
|
|
22987
23102
|
type: "integer",
|
|
22988
|
-
|
|
23103
|
+
minimum: 1,
|
|
23104
|
+
description: "Optional maximum number of tasks to return per section (In Progress / Backlog / Blocked). Omit to return all."
|
|
23105
|
+
},
|
|
23106
|
+
fields: {
|
|
23107
|
+
type: "string",
|
|
23108
|
+
description: `Optional sparse-field projection \u2014 comma-separated column names, e.g. "title,status". Returns a compact table of ONLY those columns (id is always included) instead of the full per-task block, which is far cheaper on session context. Allowed: ${BUILD_LIST_FIELDS.join(", ")}. An unknown name is an error.`
|
|
23109
|
+
},
|
|
23110
|
+
meta: {
|
|
23111
|
+
type: "boolean",
|
|
23112
|
+
description: "Append a {total, filtered, returned, limit, offset} envelope so you can tell a truncated result from a complete one. Default false."
|
|
22989
23113
|
}
|
|
22990
23114
|
},
|
|
22991
23115
|
required: []
|
|
@@ -23250,50 +23374,74 @@ var AD_CONFLICT_GATE_INSTRUCTION = "\n**AD conflict gate:** If this task conflic
|
|
|
23250
23374
|
function hasReportFields(args) {
|
|
23251
23375
|
return !!(args.completed || args.effort || args.estimated_effort || args.surprises || args.discovered_issues || args.architecture_notes);
|
|
23252
23376
|
}
|
|
23253
|
-
async function handleBuildList(adapter2, config2) {
|
|
23377
|
+
async function handleBuildList(adapter2, config2, args = {}) {
|
|
23378
|
+
const projection = resolveFields(args.fields, BUILD_LIST_FIELDS, "build_list");
|
|
23379
|
+
if (!projection.ok) return errorResponse(projection.error);
|
|
23380
|
+
const fields = projection.fields;
|
|
23381
|
+
const emitMeta = wantsMeta(args.meta);
|
|
23382
|
+
const rawLimit = args.limit;
|
|
23383
|
+
const limit = typeof rawLimit === "number" && Number.isInteger(rawLimit) && rawLimit > 0 ? rawLimit : null;
|
|
23384
|
+
const cap = (rows) => limit === null ? rows : rows.slice(0, limit);
|
|
23254
23385
|
const result = await listBuilds(adapter2, config2);
|
|
23255
23386
|
const warningPrefix = result.warnings.length > 0 ? result.warnings.map((w) => `> ${w}`).join("\n") + "\n\n" : "";
|
|
23387
|
+
const emptyMeta = () => emitMeta ? { total: result.totalTasks, filtered: 0, returned: 0, limit, offset: 0 } : null;
|
|
23256
23388
|
if (result.isEmpty) {
|
|
23257
23389
|
if (result.currentCycle === 0) {
|
|
23258
|
-
return textResponse(warningPrefix + `No tasks found \u2014 this looks like a new project. Run \`setup\` to initialise your project, then \`plan\` to create your first cycle
|
|
23390
|
+
return textResponse(withMeta(warningPrefix + `No tasks found \u2014 this looks like a new project. Run \`setup\` to initialise your project, then \`plan\` to create your first cycle.`, emptyMeta()));
|
|
23259
23391
|
}
|
|
23260
|
-
return textResponse(warningPrefix + `**Cycle ${result.currentCycle}** \u2014 Board is empty. Run \`plan\` to create your next cycle
|
|
23392
|
+
return textResponse(withMeta(warningPrefix + `**Cycle ${result.currentCycle}** \u2014 Board is empty. Run \`plan\` to create your next cycle.`, emptyMeta()));
|
|
23261
23393
|
}
|
|
23262
23394
|
if (result.noHandoffs) {
|
|
23263
|
-
return textResponse(warningPrefix + `**Cycle ${result.currentCycle}** \u2014 No tasks with BUILD HANDOFFs. Run \`plan\` to generate your next cycle tasks
|
|
23395
|
+
return textResponse(withMeta(warningPrefix + `**Cycle ${result.currentCycle}** \u2014 No tasks with BUILD HANDOFFs. Run \`plan\` to generate your next cycle tasks.`, emptyMeta()));
|
|
23264
23396
|
}
|
|
23397
|
+
const inProgress = cap(result.inProgress);
|
|
23398
|
+
const backlog = cap(result.backlog);
|
|
23399
|
+
const blocked = cap(result.blocked);
|
|
23265
23400
|
const lines = [];
|
|
23266
23401
|
if (warningPrefix) {
|
|
23267
23402
|
lines.push(warningPrefix.trimEnd(), "");
|
|
23268
23403
|
}
|
|
23269
23404
|
const totalCount = result.sorted.length + result.blocked.length;
|
|
23270
23405
|
lines.push(`**Cycle ${result.currentCycle}** \u2014 ${totalCount} tasks with BUILD HANDOFFs:`, "");
|
|
23271
|
-
if (
|
|
23272
|
-
|
|
23273
|
-
|
|
23274
|
-
lines.push(
|
|
23275
|
-
}
|
|
23276
|
-
|
|
23277
|
-
|
|
23278
|
-
|
|
23279
|
-
|
|
23280
|
-
|
|
23281
|
-
|
|
23282
|
-
|
|
23283
|
-
lines.push(
|
|
23406
|
+
if (fields) {
|
|
23407
|
+
const columns = selectColumns(BUILD_LIST_COLUMNS, fields);
|
|
23408
|
+
const rows = [...inProgress, ...backlog, ...blocked.map((b2) => b2.task)].map((t) => columns.map((c) => c.value(t)));
|
|
23409
|
+
lines.push(renderTable(columns.map((c) => c.header), rows));
|
|
23410
|
+
} else {
|
|
23411
|
+
if (inProgress.length > 0) {
|
|
23412
|
+
lines.push(`## In Progress (${result.inProgress.length})`);
|
|
23413
|
+
lines.push(...inProgress.map(formatListItem));
|
|
23414
|
+
lines.push("");
|
|
23415
|
+
}
|
|
23416
|
+
if (backlog.length > 0) {
|
|
23417
|
+
lines.push(`## Backlog (${result.backlog.length})`);
|
|
23418
|
+
lines.push(...backlog.map(formatListItem));
|
|
23419
|
+
}
|
|
23420
|
+
if (blocked.length > 0) {
|
|
23421
|
+
lines.push("", `## Blocked (${result.blocked.length}) \u2014 waiting on dependencies`);
|
|
23422
|
+
for (const { task, unresolvedDeps } of blocked) {
|
|
23423
|
+
lines.push(`- **${task.id}:** ${task.title}
|
|
23284
23424
|
Waiting on: ${unresolvedDeps.join(", ")}`);
|
|
23425
|
+
}
|
|
23285
23426
|
}
|
|
23286
|
-
|
|
23287
|
-
|
|
23288
|
-
|
|
23289
|
-
|
|
23290
|
-
|
|
23291
|
-
|
|
23292
|
-
|
|
23427
|
+
try {
|
|
23428
|
+
const comments = await adapter2.getRecentTaskComments?.(30);
|
|
23429
|
+
if (comments && comments.length > 0) {
|
|
23430
|
+
const taskIds = new Set([...result.sorted, ...result.blocked.map((b2) => b2.task)].map((t) => t.id));
|
|
23431
|
+
const section = formatTaskComments(comments, taskIds);
|
|
23432
|
+
if (section) lines.push(section);
|
|
23433
|
+
}
|
|
23434
|
+
} catch {
|
|
23293
23435
|
}
|
|
23294
|
-
} catch {
|
|
23295
23436
|
}
|
|
23296
|
-
|
|
23437
|
+
const meta = emitMeta ? {
|
|
23438
|
+
total: result.totalTasks,
|
|
23439
|
+
filtered: totalCount,
|
|
23440
|
+
returned: inProgress.length + backlog.length + blocked.length,
|
|
23441
|
+
limit,
|
|
23442
|
+
offset: 0
|
|
23443
|
+
} : null;
|
|
23444
|
+
return textResponse(withMeta(lines.join("\n"), meta));
|
|
23297
23445
|
}
|
|
23298
23446
|
async function handleBuildDescribe(adapter2, args) {
|
|
23299
23447
|
const taskId = args.task_id;
|
|
@@ -28706,7 +28854,7 @@ ${section}`;
|
|
|
28706
28854
|
}
|
|
28707
28855
|
tracker.mark("format-summary");
|
|
28708
28856
|
const deepHint = deepHousekeeping ? "" : "\n\n*Tip: pass `full: true` for Research Signals + version-drift, or `deep_housekeeping: true` to also check orphaned branches, merged-but-In-Progress tasks, unrecorded commits, unregistered docs, and stale skill forks (implies `full`).*";
|
|
28709
|
-
return textResponse(projectBannerNote + formatOrientSummary(healthResult, buildInfo, hierarchy, latestTag, config2.projectRoot, environment, subAgents, projectName, teamSummary, clientName, carryForwardRefs) + unblockNote + deferredGateNote + feedbackResolvedNote + alertsNote + ttfvNote + reconciliationNote + mergedInProgressNote + unrecordedNote + unregisteredDocsNote + staleSkillsNote + researchSignalsNote + recsNote + pendingReviewNote + patternsNote + unactionedIssuesNote + skillProposalsNote + sessionGuidanceNote + onboardingCoachingNote + versionNote + enrichmentNote + deliveryShapeNote + preBuildCheckNote + deepHint + enrichmentFilesSection);
|
|
28857
|
+
return { ...textResponse(projectBannerNote + formatOrientSummary(healthResult, buildInfo, hierarchy, latestTag, config2.projectRoot, environment, subAgents, projectName, teamSummary, clientName, carryForwardRefs) + unblockNote + deferredGateNote + feedbackResolvedNote + alertsNote + ttfvNote + reconciliationNote + mergedInProgressNote + unrecordedNote + unregisteredDocsNote + staleSkillsNote + researchSignalsNote + recsNote + pendingReviewNote + patternsNote + unactionedIssuesNote + skillProposalsNote + sessionGuidanceNote + onboardingCoachingNote + versionNote + enrichmentNote + deliveryShapeNote + preBuildCheckNote + deepHint + enrichmentFilesSection), _cycleNumber: healthResult.cycleNumber };
|
|
28710
28858
|
} catch (err) {
|
|
28711
28859
|
const message = err instanceof Error ? err.message : String(err);
|
|
28712
28860
|
const isKnownFriendly = /^(Orient failed|Project not found|No project|Setup required)/i.test(message);
|
|
@@ -29810,6 +29958,15 @@ function readLlmResponse(args) {
|
|
|
29810
29958
|
}
|
|
29811
29959
|
|
|
29812
29960
|
// src/tools/ad-view.ts
|
|
29961
|
+
var AD_COLUMNS = [
|
|
29962
|
+
{ name: "id", header: "AD", value: (d) => d.id },
|
|
29963
|
+
{ name: "title", header: "Title", value: (d) => d.title },
|
|
29964
|
+
{ name: "confidence", header: "Confidence", value: (d) => d.confidence },
|
|
29965
|
+
{ name: "superseded", header: "Superseded", value: (d) => d.superseded ? "yes" : "no" },
|
|
29966
|
+
{ name: "supersededBy", header: "Superseded By", value: (d) => d.supersededBy ?? "-" },
|
|
29967
|
+
{ name: "body", header: "Body", value: (d) => d.body.replace(/\s+/g, " ").trim() }
|
|
29968
|
+
];
|
|
29969
|
+
var AD_FIELDS = AD_COLUMNS.map((c) => c.name);
|
|
29813
29970
|
var adViewTool = {
|
|
29814
29971
|
name: "ad_view",
|
|
29815
29972
|
description: "View one or all Active Decisions with full bodies. Use when you need to read the complete reasoning and evidence behind a specific AD before running strategy_change.",
|
|
@@ -29824,6 +29981,24 @@ var adViewTool = {
|
|
|
29824
29981
|
include_superseded: {
|
|
29825
29982
|
type: "boolean",
|
|
29826
29983
|
description: "Include superseded (retired) ADs. Default false."
|
|
29984
|
+
},
|
|
29985
|
+
fields: {
|
|
29986
|
+
type: "string",
|
|
29987
|
+
description: `Optional sparse-field projection \u2014 comma-separated field names, e.g. "title,confidence". Returns a compact table of ONLY those fields (id is always included) instead of full AD bodies, which is far cheaper on session context when you just need the index. Allowed: ${AD_FIELDS.join(", ")}. An unknown name is an error.`
|
|
29988
|
+
},
|
|
29989
|
+
limit: {
|
|
29990
|
+
type: "integer",
|
|
29991
|
+
minimum: 1,
|
|
29992
|
+
description: "Max decisions to return. Omit to return all."
|
|
29993
|
+
},
|
|
29994
|
+
offset: {
|
|
29995
|
+
type: "integer",
|
|
29996
|
+
minimum: 0,
|
|
29997
|
+
description: "Skip the first N decisions (pagination). Default 0."
|
|
29998
|
+
},
|
|
29999
|
+
meta: {
|
|
30000
|
+
type: "boolean",
|
|
30001
|
+
description: "Append a {total, filtered, returned, limit, offset} envelope so you can tell a truncated result from a complete one. Default false."
|
|
29827
30002
|
}
|
|
29828
30003
|
},
|
|
29829
30004
|
required: []
|
|
@@ -29832,6 +30007,14 @@ var adViewTool = {
|
|
|
29832
30007
|
async function handleAdView(adapter2, args) {
|
|
29833
30008
|
const adId = args.ad_id;
|
|
29834
30009
|
const includeSuperseded = args.include_superseded === true;
|
|
30010
|
+
const projection = resolveFields(args.fields, AD_FIELDS, "ad_view");
|
|
30011
|
+
if (!projection.ok) return errorResponse(projection.error);
|
|
30012
|
+
const fields = projection.fields;
|
|
30013
|
+
const emitMeta = wantsMeta(args.meta);
|
|
30014
|
+
const rawLimit = args.limit;
|
|
30015
|
+
const limit = typeof rawLimit === "number" && Number.isInteger(rawLimit) && rawLimit > 0 ? rawLimit : null;
|
|
30016
|
+
const rawOffset = args.offset;
|
|
30017
|
+
const offset = typeof rawOffset === "number" && Number.isInteger(rawOffset) && rawOffset > 0 ? rawOffset : 0;
|
|
29835
30018
|
let decisions;
|
|
29836
30019
|
try {
|
|
29837
30020
|
decisions = await adapter2.getActiveDecisions({ includeRetired: includeSuperseded });
|
|
@@ -29844,26 +30027,79 @@ async function handleAdView(adapter2, args) {
|
|
|
29844
30027
|
const ids = decisions.map((d) => d.id).join(", ");
|
|
29845
30028
|
return errorResponse(`AD not found: ${adId}. Available: ${ids || "none"}`);
|
|
29846
30029
|
}
|
|
30030
|
+
const meta2 = emitMeta ? { total: decisions.length, filtered: 1, returned: 1, limit: null, offset: 0 } : null;
|
|
30031
|
+
if (fields) {
|
|
30032
|
+
const columns = selectColumns(AD_COLUMNS, fields);
|
|
30033
|
+
const body = columns.map((c) => `- **${c.header}:** ${c.value(target)}`).join("\n");
|
|
30034
|
+
return textResponse(withMeta(body, meta2));
|
|
30035
|
+
}
|
|
29847
30036
|
const supersededNote = target.superseded ? ` [SUPERSEDED by ${target.supersededBy}]` : "";
|
|
29848
30037
|
return textResponse(
|
|
29849
|
-
`## ${target.id}: ${target.title} [${target.confidence}]${supersededNote}
|
|
30038
|
+
withMeta(`## ${target.id}: ${target.title} [${target.confidence}]${supersededNote}
|
|
29850
30039
|
|
|
29851
|
-
${target.body}
|
|
30040
|
+
${target.body}`, meta2)
|
|
29852
30041
|
);
|
|
29853
30042
|
}
|
|
29854
30043
|
const filtered = includeSuperseded ? decisions : decisions.filter((d) => !d.superseded);
|
|
29855
|
-
|
|
29856
|
-
|
|
30044
|
+
const paged = limit === null && offset === 0 ? filtered : filtered.slice(offset, limit === null ? void 0 : offset + limit);
|
|
30045
|
+
const meta = emitMeta ? { total: decisions.length, filtered: filtered.length, returned: paged.length, limit, offset } : null;
|
|
30046
|
+
if (paged.length === 0) {
|
|
30047
|
+
return textResponse(withMeta("No active decisions found.", meta));
|
|
30048
|
+
}
|
|
30049
|
+
if (fields) {
|
|
30050
|
+
const columns = selectColumns(AD_COLUMNS, fields);
|
|
30051
|
+
const rows = paged.map((d) => columns.map((c) => c.value(d)));
|
|
30052
|
+
return textResponse(withMeta(renderTable(columns.map((c) => c.header), rows), meta));
|
|
29857
30053
|
}
|
|
29858
|
-
const formatted =
|
|
30054
|
+
const formatted = paged.map((d) => {
|
|
29859
30055
|
const supersededNote = d.superseded ? ` [SUPERSEDED by ${d.supersededBy}]` : "";
|
|
29860
30056
|
return `## ${d.id}: ${d.title} [${d.confidence}]${supersededNote}
|
|
29861
30057
|
|
|
29862
30058
|
${d.body}`;
|
|
29863
30059
|
}).join("\n\n---\n\n");
|
|
29864
|
-
return textResponse(`# Active Decisions (${
|
|
30060
|
+
return textResponse(withMeta(`# Active Decisions (${paged.length})
|
|
29865
30061
|
|
|
29866
|
-
${formatted}
|
|
30062
|
+
${formatted}`, meta));
|
|
30063
|
+
}
|
|
30064
|
+
|
|
30065
|
+
// src/lib/tool-telemetry.ts
|
|
30066
|
+
var INSTRUMENTED_TOOLS = /* @__PURE__ */ new Set([
|
|
30067
|
+
"plan",
|
|
30068
|
+
"orient",
|
|
30069
|
+
"build_execute",
|
|
30070
|
+
"review_submit",
|
|
30071
|
+
"release",
|
|
30072
|
+
"strategy_review"
|
|
30073
|
+
]);
|
|
30074
|
+
var WRAPPER_INSTRUMENTED_TOOLS = new Set(
|
|
30075
|
+
[...INSTRUMENTED_TOOLS].filter((t) => t !== "plan")
|
|
30076
|
+
);
|
|
30077
|
+
function measureResultBytes(content) {
|
|
30078
|
+
let total = 0;
|
|
30079
|
+
for (const part of content) {
|
|
30080
|
+
if (typeof part.text === "string") total += Buffer.byteLength(part.text, "utf-8");
|
|
30081
|
+
}
|
|
30082
|
+
return total;
|
|
30083
|
+
}
|
|
30084
|
+
function recordToolRun(adapter2, sample) {
|
|
30085
|
+
if (!WRAPPER_INSTRUMENTED_TOOLS.has(sample.toolName)) return;
|
|
30086
|
+
if (typeof adapter2.insertToolRun !== "function") return;
|
|
30087
|
+
try {
|
|
30088
|
+
const promise = adapter2.insertToolRun({
|
|
30089
|
+
toolName: sample.toolName,
|
|
30090
|
+
contextBytes: sample.contextBytes,
|
|
30091
|
+
durationMs: sample.durationMs,
|
|
30092
|
+
cycleNumber: sample.cycleNumber ?? null,
|
|
30093
|
+
source: "mcp-server"
|
|
30094
|
+
});
|
|
30095
|
+
if (promise && typeof promise.catch === "function") {
|
|
30096
|
+
promise.catch((err) => {
|
|
30097
|
+
console.error(`[telemetry] insertToolRun failed (non-blocking): ${err instanceof Error ? err.message : String(err)}`);
|
|
30098
|
+
});
|
|
30099
|
+
}
|
|
30100
|
+
} catch (err) {
|
|
30101
|
+
console.error(`[telemetry] insertToolRun threw (non-blocking): ${err instanceof Error ? err.message : String(err)}`);
|
|
30102
|
+
}
|
|
29867
30103
|
}
|
|
29868
30104
|
|
|
29869
30105
|
// src/tools/learning-action.ts
|
|
@@ -31160,7 +31396,7 @@ function createServer(adapter2, config2) {
|
|
|
31160
31396
|
case "setup":
|
|
31161
31397
|
return handleSetup(adapter2, config2, safeArgs, server2.getClientVersion()?.name);
|
|
31162
31398
|
case "build_list":
|
|
31163
|
-
return handleBuildList(adapter2, config2);
|
|
31399
|
+
return handleBuildList(adapter2, config2, safeArgs);
|
|
31164
31400
|
case "build_describe":
|
|
31165
31401
|
return handleBuildDescribe(adapter2, safeArgs);
|
|
31166
31402
|
case "build_execute":
|
|
@@ -31289,9 +31525,17 @@ ${usageLine(decision.usage)}`;
|
|
|
31289
31525
|
const usage = result._usage;
|
|
31290
31526
|
const contextBytes = result._contextBytes;
|
|
31291
31527
|
const contextUtilisation = result._contextUtilisation;
|
|
31528
|
+
const toolCycleNumber = result._cycleNumber;
|
|
31292
31529
|
delete result._usage;
|
|
31293
31530
|
delete result._contextBytes;
|
|
31294
31531
|
delete result._contextUtilisation;
|
|
31532
|
+
delete result._cycleNumber;
|
|
31533
|
+
recordToolRun(adapter2, {
|
|
31534
|
+
toolName: name,
|
|
31535
|
+
contextBytes: measureResultBytes(result.content),
|
|
31536
|
+
durationMs: elapsed,
|
|
31537
|
+
cycleNumber: toolCycleNumber ?? null
|
|
31538
|
+
});
|
|
31295
31539
|
const isError = result.content.some((c) => c.text.startsWith("Error:") || c.text.startsWith("\u274C"));
|
|
31296
31540
|
recordToolOutcome(!isError, callerKey);
|
|
31297
31541
|
try {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@papi-ai/server",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.72",
|
|
4
4
|
"description": "PAPI MCP server — AI-powered sprint planning, build execution, and strategy review for software projects",
|
|
5
5
|
"license": "Elastic-2.0",
|
|
6
6
|
"mcpName": "io.github.getpapi/papi",
|