@papi-ai/server 0.7.71 → 0.7.73
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 +336 -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
|
}
|
|
@@ -11207,6 +11214,55 @@ function formatBlockerWaiting(blocker) {
|
|
|
11207
11214
|
}
|
|
11208
11215
|
}
|
|
11209
11216
|
|
|
11217
|
+
// src/lib/tool-telemetry.ts
|
|
11218
|
+
var INSTRUMENTED_TOOLS = /* @__PURE__ */ new Set([
|
|
11219
|
+
"plan",
|
|
11220
|
+
"orient",
|
|
11221
|
+
"build_execute",
|
|
11222
|
+
"review_submit",
|
|
11223
|
+
"release",
|
|
11224
|
+
"strategy_review"
|
|
11225
|
+
]);
|
|
11226
|
+
var WRAPPER_INSTRUMENTED_TOOLS = new Set(
|
|
11227
|
+
[...INSTRUMENTED_TOOLS].filter((t) => t !== "plan")
|
|
11228
|
+
);
|
|
11229
|
+
var PLAN_PREPARE_TOOL_NAME = "plan:prepare";
|
|
11230
|
+
function measureResultBytes(content) {
|
|
11231
|
+
let total = 0;
|
|
11232
|
+
for (const part of content) {
|
|
11233
|
+
if (typeof part.text === "string") total += Buffer.byteLength(part.text, "utf-8");
|
|
11234
|
+
}
|
|
11235
|
+
return total;
|
|
11236
|
+
}
|
|
11237
|
+
function recordToolRun(adapter2, sample) {
|
|
11238
|
+
if (sample.toolName !== PLAN_PREPARE_TOOL_NAME && !WRAPPER_INSTRUMENTED_TOOLS.has(sample.toolName)) return;
|
|
11239
|
+
if (typeof adapter2.insertToolRun !== "function") return;
|
|
11240
|
+
try {
|
|
11241
|
+
const promise = adapter2.insertToolRun({
|
|
11242
|
+
toolName: sample.toolName,
|
|
11243
|
+
contextBytes: sample.contextBytes,
|
|
11244
|
+
durationMs: sample.durationMs,
|
|
11245
|
+
cycleNumber: sample.cycleNumber ?? null,
|
|
11246
|
+
source: "mcp-server"
|
|
11247
|
+
});
|
|
11248
|
+
if (promise && typeof promise.catch === "function") {
|
|
11249
|
+
promise.catch((err) => {
|
|
11250
|
+
console.error(`[telemetry] insertToolRun failed (non-blocking): ${err instanceof Error ? err.message : String(err)}`);
|
|
11251
|
+
});
|
|
11252
|
+
}
|
|
11253
|
+
} catch (err) {
|
|
11254
|
+
console.error(`[telemetry] insertToolRun threw (non-blocking): ${err instanceof Error ? err.message : String(err)}`);
|
|
11255
|
+
}
|
|
11256
|
+
}
|
|
11257
|
+
function recordPlanPrepareRun(adapter2, sample) {
|
|
11258
|
+
recordToolRun(adapter2, {
|
|
11259
|
+
toolName: PLAN_PREPARE_TOOL_NAME,
|
|
11260
|
+
contextBytes: sample.contextBytes,
|
|
11261
|
+
durationMs: sample.durationMs,
|
|
11262
|
+
cycleNumber: sample.cycleNumber ?? null
|
|
11263
|
+
});
|
|
11264
|
+
}
|
|
11265
|
+
|
|
11210
11266
|
// src/lib/visibility-inheritance.ts
|
|
11211
11267
|
var TIER_RANK = {
|
|
11212
11268
|
public: 0,
|
|
@@ -13000,6 +13056,7 @@ async function preparePlan(adapter2, config2, filters, focus, force, handoffsOnl
|
|
|
13000
13056
|
console.error(`[plan-perf] contextBytes=${contextBytes2} (handoffs-only)`);
|
|
13001
13057
|
const planSystemPrompt2 = await getPrompt("plan-system");
|
|
13002
13058
|
await recordPlanGenerationActive(tracker, incomingCycle);
|
|
13059
|
+
recordPlanPrepareRun(adapter2, { contextBytes: contextBytes2, durationMs: totalMs2, cycleNumber: incomingCycle });
|
|
13003
13060
|
return {
|
|
13004
13061
|
mode: "full",
|
|
13005
13062
|
// apply phase treats it the same
|
|
@@ -13061,6 +13118,7 @@ async function preparePlan(adapter2, config2, filters, focus, force, handoffsOnl
|
|
|
13061
13118
|
}
|
|
13062
13119
|
const planSystemPrompt = await getPrompt("plan-system");
|
|
13063
13120
|
await recordPlanGenerationActive(tracker, incomingCycle);
|
|
13121
|
+
recordPlanPrepareRun(adapter2, { contextBytes, durationMs: totalMs, cycleNumber: incomingCycle });
|
|
13064
13122
|
return {
|
|
13065
13123
|
mode,
|
|
13066
13124
|
cycleNumber,
|
|
@@ -16918,7 +16976,9 @@ async function viewBoard(adapter2, phaseFilter, options) {
|
|
|
16918
16976
|
total,
|
|
16919
16977
|
returned: paged.length,
|
|
16920
16978
|
offset,
|
|
16921
|
-
hasMore: offset + paged.length < total
|
|
16979
|
+
hasMore: offset + paged.length < total,
|
|
16980
|
+
totalUnfiltered: allTasks.length,
|
|
16981
|
+
limit
|
|
16922
16982
|
};
|
|
16923
16983
|
}
|
|
16924
16984
|
async function viewBoardSummary(adapter2) {
|
|
@@ -16966,7 +17026,87 @@ async function archiveTasks(adapter2, phases, statuses) {
|
|
|
16966
17026
|
return { archivedCount: result.archivedCount, phases };
|
|
16967
17027
|
}
|
|
16968
17028
|
|
|
17029
|
+
// src/lib/projection.ts
|
|
17030
|
+
function resolveFields(raw, allowed, toolName) {
|
|
17031
|
+
if (raw === void 0 || raw === null) return { ok: true, fields: null };
|
|
17032
|
+
if (typeof raw !== "string") {
|
|
17033
|
+
return { ok: false, error: `${toolName}: fields must be a comma-separated string (got ${typeof raw}).` };
|
|
17034
|
+
}
|
|
17035
|
+
const trimmed = raw.trim();
|
|
17036
|
+
if (trimmed === "") return { ok: true, fields: null };
|
|
17037
|
+
const canonical = new Map(allowed.map((a) => [a.toLowerCase(), a]));
|
|
17038
|
+
const requested = [];
|
|
17039
|
+
for (const part of trimmed.split(",")) {
|
|
17040
|
+
const token = part.trim();
|
|
17041
|
+
if (token === "") continue;
|
|
17042
|
+
const hit = canonical.get(token.toLowerCase());
|
|
17043
|
+
if (!hit) {
|
|
17044
|
+
return {
|
|
17045
|
+
ok: false,
|
|
17046
|
+
error: `${toolName}: unknown field "${token}". Allowed fields: ${allowed.join(", ")}.`
|
|
17047
|
+
};
|
|
17048
|
+
}
|
|
17049
|
+
if (!requested.includes(hit)) requested.push(hit);
|
|
17050
|
+
}
|
|
17051
|
+
if (requested.length === 0) return { ok: true, fields: null };
|
|
17052
|
+
if (!requested.includes("id")) requested.unshift("id");
|
|
17053
|
+
return { ok: true, fields: requested };
|
|
17054
|
+
}
|
|
17055
|
+
function selectColumns(columns, fields) {
|
|
17056
|
+
if (!fields) return columns;
|
|
17057
|
+
const byName = new Map(columns.map((c) => [c.name, c]));
|
|
17058
|
+
return fields.map((f) => byName.get(f)).filter((c) => c !== void 0);
|
|
17059
|
+
}
|
|
17060
|
+
function wantsMeta(raw) {
|
|
17061
|
+
return raw === true || raw === "true";
|
|
17062
|
+
}
|
|
17063
|
+
function formatMeta(meta) {
|
|
17064
|
+
return `**meta:** ${JSON.stringify(meta)}`;
|
|
17065
|
+
}
|
|
17066
|
+
function withMeta(body, meta) {
|
|
17067
|
+
return meta ? `${body}
|
|
17068
|
+
|
|
17069
|
+
${formatMeta(meta)}` : body;
|
|
17070
|
+
}
|
|
17071
|
+
function pad(value, width) {
|
|
17072
|
+
return value.length >= width ? value : value + " ".repeat(width - value.length);
|
|
17073
|
+
}
|
|
17074
|
+
function renderTable(headers, rows) {
|
|
17075
|
+
const widths = headers.map((h, i) => Math.max(h.length, ...rows.map((r) => r[i].length)));
|
|
17076
|
+
const headerLine = headers.map((h, i) => pad(h, widths[i])).join(" | ");
|
|
17077
|
+
const separator = widths.map((w) => "-".repeat(w)).join(" | ");
|
|
17078
|
+
const dataLines = rows.map((row) => row.map((cell, i) => pad(cell, widths[i])).join(" | "));
|
|
17079
|
+
return [headerLine, separator, ...dataLines].join("\n");
|
|
17080
|
+
}
|
|
17081
|
+
|
|
16969
17082
|
// src/tools/board.ts
|
|
17083
|
+
var BOARD_COLUMNS = [
|
|
17084
|
+
{ name: "priority", header: "Priority", value: (t) => t.priority },
|
|
17085
|
+
{ name: "id", header: "Task", value: (t) => t.id },
|
|
17086
|
+
{ name: "title", header: "Summary", value: (t) => truncateTitle(t.title) },
|
|
17087
|
+
{ name: "status", header: "Status", value: (t) => t.status },
|
|
17088
|
+
{ name: "cycle", header: "Cycle", value: (t) => t.cycle != null ? String(t.cycle) : "-" },
|
|
17089
|
+
{ name: "phase", header: "Phase", value: (t) => t.phase ?? "-" },
|
|
17090
|
+
{ name: "module", header: "Module", value: (t) => t.module ?? "-" },
|
|
17091
|
+
{ name: "epic", header: "Epic", value: (t) => t.epic ?? "-" },
|
|
17092
|
+
{ name: "complexity", header: "Effort", value: (t) => t.complexity ?? "-" },
|
|
17093
|
+
{ name: "createdAt", header: "Created", value: (t) => t.createdAt ?? "-" },
|
|
17094
|
+
{ name: "source", header: "Source", value: (t) => t.source ?? "-" }
|
|
17095
|
+
];
|
|
17096
|
+
var BOARD_FIELDS = BOARD_COLUMNS.map((c) => c.name);
|
|
17097
|
+
var BOARD_SINGLE_LABELS = {
|
|
17098
|
+
id: "Task",
|
|
17099
|
+
title: "Title",
|
|
17100
|
+
status: "Status",
|
|
17101
|
+
priority: "Priority",
|
|
17102
|
+
cycle: "Cycle",
|
|
17103
|
+
phase: "Phase",
|
|
17104
|
+
module: "Module",
|
|
17105
|
+
epic: "Epic",
|
|
17106
|
+
complexity: "Effort",
|
|
17107
|
+
createdAt: "Created",
|
|
17108
|
+
source: "Source"
|
|
17109
|
+
};
|
|
16970
17110
|
var boardViewTool = {
|
|
16971
17111
|
name: "board_view",
|
|
16972
17112
|
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 +17151,14 @@ var boardViewTool = {
|
|
|
17011
17151
|
type: "string",
|
|
17012
17152
|
enum: ["full", "summary"],
|
|
17013
17153
|
description: 'Output mode: "full" (default) shows task table, "summary" shows counts only.'
|
|
17154
|
+
},
|
|
17155
|
+
fields: {
|
|
17156
|
+
type: "string",
|
|
17157
|
+
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.`
|
|
17158
|
+
},
|
|
17159
|
+
meta: {
|
|
17160
|
+
type: "boolean",
|
|
17161
|
+
description: "Append a {total, filtered, returned, limit, offset} envelope so you can tell a truncated result from a complete one. Default false."
|
|
17014
17162
|
}
|
|
17015
17163
|
},
|
|
17016
17164
|
required: []
|
|
@@ -17175,14 +17323,27 @@ var boardEditTool = {
|
|
|
17175
17323
|
required: ["task_id"]
|
|
17176
17324
|
}
|
|
17177
17325
|
};
|
|
17178
|
-
function pad(value, width) {
|
|
17179
|
-
return value.length >= width ? value : value + " ".repeat(width - value.length);
|
|
17180
|
-
}
|
|
17181
17326
|
var TITLE_MAX = 80;
|
|
17182
17327
|
function truncateTitle(title) {
|
|
17183
17328
|
return title.length > TITLE_MAX ? `${title.slice(0, TITLE_MAX - 1)}\u2026` : title;
|
|
17184
17329
|
}
|
|
17185
|
-
function formatSingleTask(t) {
|
|
17330
|
+
function formatSingleTask(t, fields) {
|
|
17331
|
+
if (fields) {
|
|
17332
|
+
const values2 = {
|
|
17333
|
+
id: t.id,
|
|
17334
|
+
title: t.title,
|
|
17335
|
+
status: t.status,
|
|
17336
|
+
priority: t.priority,
|
|
17337
|
+
cycle: t.cycle != null ? String(t.cycle) : "-",
|
|
17338
|
+
phase: t.phase ?? "-",
|
|
17339
|
+
module: t.module ?? "-",
|
|
17340
|
+
epic: t.epic ?? "-",
|
|
17341
|
+
complexity: t.complexity ?? "-",
|
|
17342
|
+
createdAt: t.createdAt ?? "-",
|
|
17343
|
+
source: t.source ?? "-"
|
|
17344
|
+
};
|
|
17345
|
+
return fields.map((f) => `- **${BOARD_SINGLE_LABELS[f] ?? f}:** ${values2[f]}`).join("\n");
|
|
17346
|
+
}
|
|
17186
17347
|
const lines = [
|
|
17187
17348
|
`**${t.id} \u2014 ${t.title}**`,
|
|
17188
17349
|
"",
|
|
@@ -17199,41 +17360,20 @@ function formatSingleTask(t) {
|
|
|
17199
17360
|
if (t.why?.trim()) lines.push(`- **Why:** ${t.why.trim()}`);
|
|
17200
17361
|
return lines.join("\n");
|
|
17201
17362
|
}
|
|
17202
|
-
function formatBoard(result) {
|
|
17363
|
+
function formatBoard(result, fields) {
|
|
17203
17364
|
if (result.tasks.length === 0) {
|
|
17204
17365
|
return "No tasks found.";
|
|
17205
17366
|
}
|
|
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
|
-
);
|
|
17367
|
+
const columns = selectColumns(BOARD_COLUMNS, fields);
|
|
17368
|
+
const headers = columns.map((c) => c.header);
|
|
17369
|
+
const rows = result.tasks.map((t) => columns.map((c) => c.value(t)));
|
|
17228
17370
|
const lines = [];
|
|
17229
17371
|
lines.push(`**${result.returned} of ${result.total} tasks**`);
|
|
17230
17372
|
if (result.hasMore) {
|
|
17231
17373
|
lines.push(`_Showing ${result.offset + 1}\u2013${result.offset + result.returned}. Use offset=${result.offset + result.returned} to see more._`);
|
|
17232
17374
|
}
|
|
17233
17375
|
lines.push("");
|
|
17234
|
-
lines.push(
|
|
17235
|
-
lines.push(separator);
|
|
17236
|
-
lines.push(...dataLines);
|
|
17376
|
+
lines.push(renderTable(headers, rows));
|
|
17237
17377
|
return lines.join("\n");
|
|
17238
17378
|
}
|
|
17239
17379
|
function formatSummary(summary) {
|
|
@@ -17255,15 +17395,21 @@ function formatSummary(summary) {
|
|
|
17255
17395
|
}
|
|
17256
17396
|
async function handleBoardView(adapter2, args) {
|
|
17257
17397
|
const mode = args.mode;
|
|
17398
|
+
const projection = resolveFields(args.fields, BOARD_FIELDS, "board_view");
|
|
17399
|
+
if (!projection.ok) return errorResponse(projection.error);
|
|
17400
|
+
const fields = projection.fields;
|
|
17401
|
+
const emitMeta = wantsMeta(args.meta);
|
|
17258
17402
|
if (mode === "summary") {
|
|
17259
17403
|
const summary = await viewBoardSummary(adapter2);
|
|
17260
|
-
|
|
17404
|
+
const meta2 = emitMeta ? { total: summary.total, filtered: summary.total, returned: summary.total, limit: null, offset: 0 } : null;
|
|
17405
|
+
return textResponse(withMeta(formatSummary(summary), meta2));
|
|
17261
17406
|
}
|
|
17262
17407
|
const taskIdArg = args.task_id ?? args.display_id;
|
|
17263
17408
|
if (taskIdArg) {
|
|
17264
17409
|
const task = await adapter2.getTask(taskIdArg);
|
|
17265
17410
|
if (!task) return errorResponse(`Task ${taskIdArg} not found.`);
|
|
17266
|
-
|
|
17411
|
+
const meta2 = emitMeta ? { total: 1, filtered: 1, returned: 1, limit: null, offset: 0 } : null;
|
|
17412
|
+
return textResponse(withMeta(formatSingleTask(task, fields), meta2));
|
|
17267
17413
|
}
|
|
17268
17414
|
const result = await viewBoard(adapter2, void 0, {
|
|
17269
17415
|
phase: args.phase,
|
|
@@ -17273,17 +17419,26 @@ async function handleBoardView(adapter2, args) {
|
|
|
17273
17419
|
query: args.query,
|
|
17274
17420
|
cycle: args.cycle
|
|
17275
17421
|
});
|
|
17276
|
-
let output = formatBoard(result);
|
|
17277
|
-
|
|
17278
|
-
|
|
17279
|
-
|
|
17280
|
-
|
|
17281
|
-
|
|
17282
|
-
|
|
17422
|
+
let output = formatBoard(result, fields);
|
|
17423
|
+
if (!fields) {
|
|
17424
|
+
try {
|
|
17425
|
+
const comments = await adapter2.getRecentTaskComments?.(30);
|
|
17426
|
+
if (comments && comments.length > 0) {
|
|
17427
|
+
const taskIds = new Set(result.tasks.map((t) => t.id));
|
|
17428
|
+
const section = formatTaskComments(comments, taskIds, "**Task Comments:**");
|
|
17429
|
+
if (section) output += "\n" + section;
|
|
17430
|
+
}
|
|
17431
|
+
} catch {
|
|
17283
17432
|
}
|
|
17284
|
-
} catch {
|
|
17285
17433
|
}
|
|
17286
|
-
|
|
17434
|
+
const meta = emitMeta ? {
|
|
17435
|
+
total: result.totalUnfiltered,
|
|
17436
|
+
filtered: result.total,
|
|
17437
|
+
returned: result.returned,
|
|
17438
|
+
limit: result.limit,
|
|
17439
|
+
offset: result.offset
|
|
17440
|
+
} : null;
|
|
17441
|
+
return textResponse(withMeta(output, meta));
|
|
17287
17442
|
}
|
|
17288
17443
|
async function handleBoardDeprioritise(adapter2, args) {
|
|
17289
17444
|
const taskId = args.task_id;
|
|
@@ -21530,7 +21685,7 @@ async function listBuilds(adapter2, config2) {
|
|
|
21530
21685
|
]);
|
|
21531
21686
|
const currentCycle = health?.totalCycles ?? 0;
|
|
21532
21687
|
if (tasks.length === 0) {
|
|
21533
|
-
return { sorted: [], inProgress: [], backlog: [], blocked: [], warnings, isEmpty: true, noHandoffs: false, currentCycle };
|
|
21688
|
+
return { sorted: [], inProgress: [], backlog: [], blocked: [], warnings, isEmpty: true, noHandoffs: false, currentCycle, totalTasks: tasks.length };
|
|
21534
21689
|
}
|
|
21535
21690
|
const withHandoff = tasks.filter((t) => {
|
|
21536
21691
|
if (!t.buildHandoff) return false;
|
|
@@ -21541,7 +21696,7 @@ async function listBuilds(adapter2, config2) {
|
|
|
21541
21696
|
return true;
|
|
21542
21697
|
});
|
|
21543
21698
|
if (withHandoff.length === 0) {
|
|
21544
|
-
return { sorted: [], inProgress: [], backlog: [], blocked: [], warnings, isEmpty: false, noHandoffs: true, currentCycle };
|
|
21699
|
+
return { sorted: [], inProgress: [], backlog: [], blocked: [], warnings, isEmpty: false, noHandoffs: true, currentCycle, totalTasks: tasks.length };
|
|
21545
21700
|
}
|
|
21546
21701
|
const buildable = [];
|
|
21547
21702
|
const blocked = [];
|
|
@@ -21556,7 +21711,7 @@ async function listBuilds(adapter2, config2) {
|
|
|
21556
21711
|
const inProgress = buildable.filter((t) => t.status === "In Progress");
|
|
21557
21712
|
const backlog = buildable.filter((t) => t.status !== "In Progress");
|
|
21558
21713
|
const sorted = [...inProgress, ...backlog];
|
|
21559
|
-
return { sorted, inProgress, backlog, blocked, warnings, isEmpty: false, noHandoffs: sorted.length === 0 && blocked.length === 0, currentCycle };
|
|
21714
|
+
return { sorted, inProgress, backlog, blocked, warnings, isEmpty: false, noHandoffs: sorted.length === 0 && blocked.length === 0, currentCycle, totalTasks: tasks.length };
|
|
21560
21715
|
}
|
|
21561
21716
|
async function describeTask(adapter2, taskId) {
|
|
21562
21717
|
const task = await adapter2.getTask(taskId);
|
|
@@ -22976,6 +23131,17 @@ async function handleDocReorder(adapter2, args) {
|
|
|
22976
23131
|
|
|
22977
23132
|
// src/tools/build.ts
|
|
22978
23133
|
init_git();
|
|
23134
|
+
var BUILD_LIST_COLUMNS = [
|
|
23135
|
+
{ name: "id", header: "Task", value: (t) => t.id },
|
|
23136
|
+
{ name: "title", header: "Summary", value: (t) => t.title },
|
|
23137
|
+
{ name: "status", header: "Status", value: (t) => t.status },
|
|
23138
|
+
{ name: "priority", header: "Priority", value: (t) => t.priority },
|
|
23139
|
+
{ name: "complexity", header: "Effort", value: (t) => t.complexity ?? "-" },
|
|
23140
|
+
{ name: "module", header: "Module", value: (t) => t.module ?? "-" },
|
|
23141
|
+
{ name: "epic", header: "Epic", value: (t) => t.epic ?? "-" },
|
|
23142
|
+
{ name: "cycle", header: "Cycle", value: (t) => t.cycle != null ? String(t.cycle) : "-" }
|
|
23143
|
+
];
|
|
23144
|
+
var BUILD_LIST_FIELDS = BUILD_LIST_COLUMNS.map((c) => c.name);
|
|
22979
23145
|
var buildListTool = {
|
|
22980
23146
|
name: "build_list",
|
|
22981
23147
|
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 +23151,16 @@ var buildListTool = {
|
|
|
22985
23151
|
properties: {
|
|
22986
23152
|
limit: {
|
|
22987
23153
|
type: "integer",
|
|
22988
|
-
|
|
23154
|
+
minimum: 1,
|
|
23155
|
+
description: "Optional maximum number of tasks to return per section (In Progress / Backlog / Blocked). Omit to return all."
|
|
23156
|
+
},
|
|
23157
|
+
fields: {
|
|
23158
|
+
type: "string",
|
|
23159
|
+
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.`
|
|
23160
|
+
},
|
|
23161
|
+
meta: {
|
|
23162
|
+
type: "boolean",
|
|
23163
|
+
description: "Append a {total, filtered, returned, limit, offset} envelope so you can tell a truncated result from a complete one. Default false."
|
|
22989
23164
|
}
|
|
22990
23165
|
},
|
|
22991
23166
|
required: []
|
|
@@ -23250,50 +23425,74 @@ var AD_CONFLICT_GATE_INSTRUCTION = "\n**AD conflict gate:** If this task conflic
|
|
|
23250
23425
|
function hasReportFields(args) {
|
|
23251
23426
|
return !!(args.completed || args.effort || args.estimated_effort || args.surprises || args.discovered_issues || args.architecture_notes);
|
|
23252
23427
|
}
|
|
23253
|
-
async function handleBuildList(adapter2, config2) {
|
|
23428
|
+
async function handleBuildList(adapter2, config2, args = {}) {
|
|
23429
|
+
const projection = resolveFields(args.fields, BUILD_LIST_FIELDS, "build_list");
|
|
23430
|
+
if (!projection.ok) return errorResponse(projection.error);
|
|
23431
|
+
const fields = projection.fields;
|
|
23432
|
+
const emitMeta = wantsMeta(args.meta);
|
|
23433
|
+
const rawLimit = args.limit;
|
|
23434
|
+
const limit = typeof rawLimit === "number" && Number.isInteger(rawLimit) && rawLimit > 0 ? rawLimit : null;
|
|
23435
|
+
const cap = (rows) => limit === null ? rows : rows.slice(0, limit);
|
|
23254
23436
|
const result = await listBuilds(adapter2, config2);
|
|
23255
23437
|
const warningPrefix = result.warnings.length > 0 ? result.warnings.map((w) => `> ${w}`).join("\n") + "\n\n" : "";
|
|
23438
|
+
const emptyMeta = () => emitMeta ? { total: result.totalTasks, filtered: 0, returned: 0, limit, offset: 0 } : null;
|
|
23256
23439
|
if (result.isEmpty) {
|
|
23257
23440
|
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
|
|
23441
|
+
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
23442
|
}
|
|
23260
|
-
return textResponse(warningPrefix + `**Cycle ${result.currentCycle}** \u2014 Board is empty. Run \`plan\` to create your next cycle
|
|
23443
|
+
return textResponse(withMeta(warningPrefix + `**Cycle ${result.currentCycle}** \u2014 Board is empty. Run \`plan\` to create your next cycle.`, emptyMeta()));
|
|
23261
23444
|
}
|
|
23262
23445
|
if (result.noHandoffs) {
|
|
23263
|
-
return textResponse(warningPrefix + `**Cycle ${result.currentCycle}** \u2014 No tasks with BUILD HANDOFFs. Run \`plan\` to generate your next cycle tasks
|
|
23446
|
+
return textResponse(withMeta(warningPrefix + `**Cycle ${result.currentCycle}** \u2014 No tasks with BUILD HANDOFFs. Run \`plan\` to generate your next cycle tasks.`, emptyMeta()));
|
|
23264
23447
|
}
|
|
23448
|
+
const inProgress = cap(result.inProgress);
|
|
23449
|
+
const backlog = cap(result.backlog);
|
|
23450
|
+
const blocked = cap(result.blocked);
|
|
23265
23451
|
const lines = [];
|
|
23266
23452
|
if (warningPrefix) {
|
|
23267
23453
|
lines.push(warningPrefix.trimEnd(), "");
|
|
23268
23454
|
}
|
|
23269
23455
|
const totalCount = result.sorted.length + result.blocked.length;
|
|
23270
23456
|
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(
|
|
23457
|
+
if (fields) {
|
|
23458
|
+
const columns = selectColumns(BUILD_LIST_COLUMNS, fields);
|
|
23459
|
+
const rows = [...inProgress, ...backlog, ...blocked.map((b2) => b2.task)].map((t) => columns.map((c) => c.value(t)));
|
|
23460
|
+
lines.push(renderTable(columns.map((c) => c.header), rows));
|
|
23461
|
+
} else {
|
|
23462
|
+
if (inProgress.length > 0) {
|
|
23463
|
+
lines.push(`## In Progress (${result.inProgress.length})`);
|
|
23464
|
+
lines.push(...inProgress.map(formatListItem));
|
|
23465
|
+
lines.push("");
|
|
23466
|
+
}
|
|
23467
|
+
if (backlog.length > 0) {
|
|
23468
|
+
lines.push(`## Backlog (${result.backlog.length})`);
|
|
23469
|
+
lines.push(...backlog.map(formatListItem));
|
|
23470
|
+
}
|
|
23471
|
+
if (blocked.length > 0) {
|
|
23472
|
+
lines.push("", `## Blocked (${result.blocked.length}) \u2014 waiting on dependencies`);
|
|
23473
|
+
for (const { task, unresolvedDeps } of blocked) {
|
|
23474
|
+
lines.push(`- **${task.id}:** ${task.title}
|
|
23284
23475
|
Waiting on: ${unresolvedDeps.join(", ")}`);
|
|
23476
|
+
}
|
|
23285
23477
|
}
|
|
23286
|
-
|
|
23287
|
-
|
|
23288
|
-
|
|
23289
|
-
|
|
23290
|
-
|
|
23291
|
-
|
|
23292
|
-
|
|
23478
|
+
try {
|
|
23479
|
+
const comments = await adapter2.getRecentTaskComments?.(30);
|
|
23480
|
+
if (comments && comments.length > 0) {
|
|
23481
|
+
const taskIds = new Set([...result.sorted, ...result.blocked.map((b2) => b2.task)].map((t) => t.id));
|
|
23482
|
+
const section = formatTaskComments(comments, taskIds);
|
|
23483
|
+
if (section) lines.push(section);
|
|
23484
|
+
}
|
|
23485
|
+
} catch {
|
|
23293
23486
|
}
|
|
23294
|
-
} catch {
|
|
23295
23487
|
}
|
|
23296
|
-
|
|
23488
|
+
const meta = emitMeta ? {
|
|
23489
|
+
total: result.totalTasks,
|
|
23490
|
+
filtered: totalCount,
|
|
23491
|
+
returned: inProgress.length + backlog.length + blocked.length,
|
|
23492
|
+
limit,
|
|
23493
|
+
offset: 0
|
|
23494
|
+
} : null;
|
|
23495
|
+
return textResponse(withMeta(lines.join("\n"), meta));
|
|
23297
23496
|
}
|
|
23298
23497
|
async function handleBuildDescribe(adapter2, args) {
|
|
23299
23498
|
const taskId = args.task_id;
|
|
@@ -28706,7 +28905,7 @@ ${section}`;
|
|
|
28706
28905
|
}
|
|
28707
28906
|
tracker.mark("format-summary");
|
|
28708
28907
|
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);
|
|
28908
|
+
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
28909
|
} catch (err) {
|
|
28711
28910
|
const message = err instanceof Error ? err.message : String(err);
|
|
28712
28911
|
const isKnownFriendly = /^(Orient failed|Project not found|No project|Setup required)/i.test(message);
|
|
@@ -29810,6 +30009,15 @@ function readLlmResponse(args) {
|
|
|
29810
30009
|
}
|
|
29811
30010
|
|
|
29812
30011
|
// src/tools/ad-view.ts
|
|
30012
|
+
var AD_COLUMNS = [
|
|
30013
|
+
{ name: "id", header: "AD", value: (d) => d.id },
|
|
30014
|
+
{ name: "title", header: "Title", value: (d) => d.title },
|
|
30015
|
+
{ name: "confidence", header: "Confidence", value: (d) => d.confidence },
|
|
30016
|
+
{ name: "superseded", header: "Superseded", value: (d) => d.superseded ? "yes" : "no" },
|
|
30017
|
+
{ name: "supersededBy", header: "Superseded By", value: (d) => d.supersededBy ?? "-" },
|
|
30018
|
+
{ name: "body", header: "Body", value: (d) => d.body.replace(/\s+/g, " ").trim() }
|
|
30019
|
+
];
|
|
30020
|
+
var AD_FIELDS = AD_COLUMNS.map((c) => c.name);
|
|
29813
30021
|
var adViewTool = {
|
|
29814
30022
|
name: "ad_view",
|
|
29815
30023
|
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 +30032,24 @@ var adViewTool = {
|
|
|
29824
30032
|
include_superseded: {
|
|
29825
30033
|
type: "boolean",
|
|
29826
30034
|
description: "Include superseded (retired) ADs. Default false."
|
|
30035
|
+
},
|
|
30036
|
+
fields: {
|
|
30037
|
+
type: "string",
|
|
30038
|
+
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.`
|
|
30039
|
+
},
|
|
30040
|
+
limit: {
|
|
30041
|
+
type: "integer",
|
|
30042
|
+
minimum: 1,
|
|
30043
|
+
description: "Max decisions to return. Omit to return all."
|
|
30044
|
+
},
|
|
30045
|
+
offset: {
|
|
30046
|
+
type: "integer",
|
|
30047
|
+
minimum: 0,
|
|
30048
|
+
description: "Skip the first N decisions (pagination). Default 0."
|
|
30049
|
+
},
|
|
30050
|
+
meta: {
|
|
30051
|
+
type: "boolean",
|
|
30052
|
+
description: "Append a {total, filtered, returned, limit, offset} envelope so you can tell a truncated result from a complete one. Default false."
|
|
29827
30053
|
}
|
|
29828
30054
|
},
|
|
29829
30055
|
required: []
|
|
@@ -29832,6 +30058,14 @@ var adViewTool = {
|
|
|
29832
30058
|
async function handleAdView(adapter2, args) {
|
|
29833
30059
|
const adId = args.ad_id;
|
|
29834
30060
|
const includeSuperseded = args.include_superseded === true;
|
|
30061
|
+
const projection = resolveFields(args.fields, AD_FIELDS, "ad_view");
|
|
30062
|
+
if (!projection.ok) return errorResponse(projection.error);
|
|
30063
|
+
const fields = projection.fields;
|
|
30064
|
+
const emitMeta = wantsMeta(args.meta);
|
|
30065
|
+
const rawLimit = args.limit;
|
|
30066
|
+
const limit = typeof rawLimit === "number" && Number.isInteger(rawLimit) && rawLimit > 0 ? rawLimit : null;
|
|
30067
|
+
const rawOffset = args.offset;
|
|
30068
|
+
const offset = typeof rawOffset === "number" && Number.isInteger(rawOffset) && rawOffset > 0 ? rawOffset : 0;
|
|
29835
30069
|
let decisions;
|
|
29836
30070
|
try {
|
|
29837
30071
|
decisions = await adapter2.getActiveDecisions({ includeRetired: includeSuperseded });
|
|
@@ -29844,26 +30078,39 @@ async function handleAdView(adapter2, args) {
|
|
|
29844
30078
|
const ids = decisions.map((d) => d.id).join(", ");
|
|
29845
30079
|
return errorResponse(`AD not found: ${adId}. Available: ${ids || "none"}`);
|
|
29846
30080
|
}
|
|
30081
|
+
const meta2 = emitMeta ? { total: decisions.length, filtered: 1, returned: 1, limit: null, offset: 0 } : null;
|
|
30082
|
+
if (fields) {
|
|
30083
|
+
const columns = selectColumns(AD_COLUMNS, fields);
|
|
30084
|
+
const body = columns.map((c) => `- **${c.header}:** ${c.value(target)}`).join("\n");
|
|
30085
|
+
return textResponse(withMeta(body, meta2));
|
|
30086
|
+
}
|
|
29847
30087
|
const supersededNote = target.superseded ? ` [SUPERSEDED by ${target.supersededBy}]` : "";
|
|
29848
30088
|
return textResponse(
|
|
29849
|
-
`## ${target.id}: ${target.title} [${target.confidence}]${supersededNote}
|
|
30089
|
+
withMeta(`## ${target.id}: ${target.title} [${target.confidence}]${supersededNote}
|
|
29850
30090
|
|
|
29851
|
-
${target.body}
|
|
30091
|
+
${target.body}`, meta2)
|
|
29852
30092
|
);
|
|
29853
30093
|
}
|
|
29854
30094
|
const filtered = includeSuperseded ? decisions : decisions.filter((d) => !d.superseded);
|
|
29855
|
-
|
|
29856
|
-
|
|
30095
|
+
const paged = limit === null && offset === 0 ? filtered : filtered.slice(offset, limit === null ? void 0 : offset + limit);
|
|
30096
|
+
const meta = emitMeta ? { total: decisions.length, filtered: filtered.length, returned: paged.length, limit, offset } : null;
|
|
30097
|
+
if (paged.length === 0) {
|
|
30098
|
+
return textResponse(withMeta("No active decisions found.", meta));
|
|
29857
30099
|
}
|
|
29858
|
-
|
|
30100
|
+
if (fields) {
|
|
30101
|
+
const columns = selectColumns(AD_COLUMNS, fields);
|
|
30102
|
+
const rows = paged.map((d) => columns.map((c) => c.value(d)));
|
|
30103
|
+
return textResponse(withMeta(renderTable(columns.map((c) => c.header), rows), meta));
|
|
30104
|
+
}
|
|
30105
|
+
const formatted = paged.map((d) => {
|
|
29859
30106
|
const supersededNote = d.superseded ? ` [SUPERSEDED by ${d.supersededBy}]` : "";
|
|
29860
30107
|
return `## ${d.id}: ${d.title} [${d.confidence}]${supersededNote}
|
|
29861
30108
|
|
|
29862
30109
|
${d.body}`;
|
|
29863
30110
|
}).join("\n\n---\n\n");
|
|
29864
|
-
return textResponse(`# Active Decisions (${
|
|
30111
|
+
return textResponse(withMeta(`# Active Decisions (${paged.length})
|
|
29865
30112
|
|
|
29866
|
-
${formatted}
|
|
30113
|
+
${formatted}`, meta));
|
|
29867
30114
|
}
|
|
29868
30115
|
|
|
29869
30116
|
// src/tools/learning-action.ts
|
|
@@ -31160,7 +31407,7 @@ function createServer(adapter2, config2) {
|
|
|
31160
31407
|
case "setup":
|
|
31161
31408
|
return handleSetup(adapter2, config2, safeArgs, server2.getClientVersion()?.name);
|
|
31162
31409
|
case "build_list":
|
|
31163
|
-
return handleBuildList(adapter2, config2);
|
|
31410
|
+
return handleBuildList(adapter2, config2, safeArgs);
|
|
31164
31411
|
case "build_describe":
|
|
31165
31412
|
return handleBuildDescribe(adapter2, safeArgs);
|
|
31166
31413
|
case "build_execute":
|
|
@@ -31289,9 +31536,17 @@ ${usageLine(decision.usage)}`;
|
|
|
31289
31536
|
const usage = result._usage;
|
|
31290
31537
|
const contextBytes = result._contextBytes;
|
|
31291
31538
|
const contextUtilisation = result._contextUtilisation;
|
|
31539
|
+
const toolCycleNumber = result._cycleNumber;
|
|
31292
31540
|
delete result._usage;
|
|
31293
31541
|
delete result._contextBytes;
|
|
31294
31542
|
delete result._contextUtilisation;
|
|
31543
|
+
delete result._cycleNumber;
|
|
31544
|
+
recordToolRun(adapter2, {
|
|
31545
|
+
toolName: name,
|
|
31546
|
+
contextBytes: measureResultBytes(result.content),
|
|
31547
|
+
durationMs: elapsed,
|
|
31548
|
+
cycleNumber: toolCycleNumber ?? null
|
|
31549
|
+
});
|
|
31295
31550
|
const isError = result.content.some((c) => c.text.startsWith("Error:") || c.text.startsWith("\u274C"));
|
|
31296
31551
|
recordToolOutcome(!isError, callerKey);
|
|
31297
31552
|
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.73",
|
|
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",
|