@papi-ai/server 0.7.65 → 0.7.66
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 +6 -0
- package/dist/index.js +193 -18
- package/package.json +1 -1
|
@@ -1785,6 +1785,12 @@ Check PAPI_PROJECT_ID in your .mcp.json config. Find your project ID in the PAPI
|
|
|
1785
1785
|
markFeedbackNotified(ids, userId) {
|
|
1786
1786
|
return this.invoke("markFeedbackNotified", [ids, userId]);
|
|
1787
1787
|
}
|
|
1788
|
+
// task-2438: the caller's own bug/idea reports. The data-proxy ignores the
|
|
1789
|
+
// client-supplied userId and scopes to the bearer-validated caller, so a user
|
|
1790
|
+
// can only ever read their OWN reports (same pattern as getUnnotifiedResolvedFeedback).
|
|
1791
|
+
listMyBugReports(userId) {
|
|
1792
|
+
return this.invoke("listMyBugReports", [userId]);
|
|
1793
|
+
}
|
|
1788
1794
|
// --- Doc Registry ---
|
|
1789
1795
|
registerDoc(entry) {
|
|
1790
1796
|
return this.invoke("registerDoc", [entry]);
|
package/dist/index.js
CHANGED
|
@@ -1902,6 +1902,12 @@ Check PAPI_PROJECT_ID in your .mcp.json config. Find your project ID in the PAPI
|
|
|
1902
1902
|
markFeedbackNotified(ids, userId) {
|
|
1903
1903
|
return this.invoke("markFeedbackNotified", [ids, userId]);
|
|
1904
1904
|
}
|
|
1905
|
+
// task-2438: the caller's own bug/idea reports. The data-proxy ignores the
|
|
1906
|
+
// client-supplied userId and scopes to the bearer-validated caller, so a user
|
|
1907
|
+
// can only ever read their OWN reports (same pattern as getUnnotifiedResolvedFeedback).
|
|
1908
|
+
listMyBugReports(userId) {
|
|
1909
|
+
return this.invoke("listMyBugReports", [userId]);
|
|
1910
|
+
}
|
|
1905
1911
|
// --- Doc Registry ---
|
|
1906
1912
|
registerDoc(entry) {
|
|
1907
1913
|
return this.invoke("registerDoc", [entry]);
|
|
@@ -19255,12 +19261,21 @@ ${command}
|
|
|
19255
19261
|
\`\`\`
|
|
19256
19262
|
PAPI never runs this command itself (AD-58) \u2014 you run it in your own environment. Turn the "Post-release deploy" capability off in the dashboard, or unset PAPI_DEPLOY, to stop this reminder.`;
|
|
19257
19263
|
}
|
|
19264
|
+
function buildPapiMetaFramingDirective(caps, inner) {
|
|
19265
|
+
if (!isCapabilityEnabled(caps, "papiMetaFraming")) return null;
|
|
19266
|
+
return inner;
|
|
19267
|
+
}
|
|
19258
19268
|
|
|
19259
19269
|
// src/services/build.ts
|
|
19260
19270
|
import { randomUUID as randomUUID11 } from "crypto";
|
|
19261
19271
|
import { readdirSync as readdirSync5, existsSync as existsSync7, readFileSync as readFileSync6, writeFileSync as writeFileSync3, unlinkSync as unlinkSync2, mkdirSync as mkdirSync2 } from "fs";
|
|
19262
19272
|
import { join as join11 } from "path";
|
|
19263
19273
|
|
|
19274
|
+
// src/lib/db-only-notices.ts
|
|
19275
|
+
var DB_ONLY_START_NOTICE = "No git repo detected \u2014 running this cycle in your project database only. Your work stays in the working tree; run `git init` (and add a remote) to enable branches, commits and PR review.";
|
|
19276
|
+
var DB_ONLY_COMPLETE_NOTICE = "No git repo \u2014 build recorded in your project database only (no commit or PR). Run `git init` (and add a remote) to enable git-backed commits and PR review.";
|
|
19277
|
+
var DB_ONLY_RELEASE_NOTICE = "No git repo detected \u2014 this release closed the cycle in your project database only. No tag, branch merge, or CHANGELOG was created. Run `git init` (and add a remote) to enable git-backed releases (tags, merges and changelog).";
|
|
19278
|
+
|
|
19264
19279
|
// src/lib/harness-capability.ts
|
|
19265
19280
|
var HARNESS_REGISTRY = {
|
|
19266
19281
|
// Local stdio CLI agents — PAPI runs git on the user's machine. Confirmed in telemetry.
|
|
@@ -20419,6 +20434,38 @@ Run \`project_switch <slug>\` to switch the active PAPI project, or verify your
|
|
|
20419
20434
|
if (gateDecision.action === "block") {
|
|
20420
20435
|
return errorResponse(gateDecision.message);
|
|
20421
20436
|
}
|
|
20437
|
+
if (!isGitAvailable() || !isGitRepo(config2.projectRoot)) {
|
|
20438
|
+
tracker.mark("db-only-close-cycle");
|
|
20439
|
+
let closed;
|
|
20440
|
+
try {
|
|
20441
|
+
closed = await closeCycleState(config2, adapter2, version, void 0, {
|
|
20442
|
+
force: force ?? false,
|
|
20443
|
+
callerUserId: gate.callerUserId
|
|
20444
|
+
});
|
|
20445
|
+
} catch (err) {
|
|
20446
|
+
return errorResponse(err instanceof Error ? err.message : String(err));
|
|
20447
|
+
}
|
|
20448
|
+
await completeRelease(tracker, {
|
|
20449
|
+
cycleClosed: closed.resolvedCycleNum > 0 ? closed.resolvedCycleNum : null,
|
|
20450
|
+
version,
|
|
20451
|
+
caps,
|
|
20452
|
+
branchMerges: [],
|
|
20453
|
+
changelogEmitted: false
|
|
20454
|
+
});
|
|
20455
|
+
const cyclePart = closed.resolvedCycleNum > 0 ? `Cycle ${closed.resolvedCycleNum}` : "The cycle";
|
|
20456
|
+
const warningsBlock = closed.warnings.length > 0 ? `
|
|
20457
|
+
\u26A0\uFE0F Warnings: ${closed.warnings.join("; ")}
|
|
20458
|
+
` : "";
|
|
20459
|
+
return textResponse(
|
|
20460
|
+
`## Release ${version} \u2014 cycle closed (database only)
|
|
20461
|
+
|
|
20462
|
+
${cyclePart} is now marked **complete** in PAPI, so \`orient\` will no longer flag "Release has not been run."
|
|
20463
|
+
` + warningsBlock + `
|
|
20464
|
+
${DB_ONLY_RELEASE_NOTICE}
|
|
20465
|
+
|
|
20466
|
+
Next: cycle closed! Run \`plan\` to start your next cycle.`
|
|
20467
|
+
);
|
|
20468
|
+
}
|
|
20422
20469
|
tracker.mark("create-release");
|
|
20423
20470
|
const result = await createRelease(config2, branch, version, adapter2, void 0, {
|
|
20424
20471
|
force: force ?? false,
|
|
@@ -21100,8 +21147,12 @@ async function startBuild(adapter2, config2, taskId, options = {}, clientName) {
|
|
|
21100
21147
|
throw err;
|
|
21101
21148
|
}
|
|
21102
21149
|
const branchLines = [];
|
|
21150
|
+
const startCaps = adapter2.getProjectInfo ? (await adapter2.getProjectInfo().catch(() => null))?.capabilities ?? {} : {};
|
|
21151
|
+
const autoBranchEnabled = isCapabilityEnabled(startCaps, "autoBranch");
|
|
21103
21152
|
if (options.light) {
|
|
21104
21153
|
branchLines.push("Light mode: skipping branch creation \u2014 working on current branch.");
|
|
21154
|
+
} else if (!autoBranchEnabled) {
|
|
21155
|
+
branchLines.push("Auto branch: skipped (Auto branch capability off) \u2014 working on current branch.");
|
|
21105
21156
|
} else if (!hasLocalWorkspace()) {
|
|
21106
21157
|
const cycleHealth = await adapter2.getCycleHealth().catch(() => null);
|
|
21107
21158
|
const hosted = hostedStartSteps(clientName, taskId, task.module, cycleHealth?.totalCycles ?? 0, config2.baseBranch);
|
|
@@ -21272,6 +21323,12 @@ async function startBuild(adapter2, config2, taskId, options = {}, clientName) {
|
|
|
21272
21323
|
}
|
|
21273
21324
|
}
|
|
21274
21325
|
}
|
|
21326
|
+
} else {
|
|
21327
|
+
if (!isGitAvailable() || !isGitRepo(config2.projectRoot)) {
|
|
21328
|
+
branchLines.push(DB_ONLY_START_NOTICE);
|
|
21329
|
+
} else {
|
|
21330
|
+
branchLines.push("Auto-commit off (PAPI_AUTO_COMMIT=false) \u2014 skipping branch creation; working on the current branch.");
|
|
21331
|
+
}
|
|
21275
21332
|
}
|
|
21276
21333
|
if (task.status !== "In Progress") {
|
|
21277
21334
|
await adapter2.updateTaskStatus(taskId, "In Progress");
|
|
@@ -21408,6 +21465,9 @@ async function completeBuild(adapter2, config2, taskId, input, options = {}, cli
|
|
|
21408
21465
|
if (!task) {
|
|
21409
21466
|
throw new Error(`Task "${taskId}" not found on the Cycle Board.`);
|
|
21410
21467
|
}
|
|
21468
|
+
const completeCaps = adapter2.getProjectInfo ? (await adapter2.getProjectInfo().catch(() => null))?.capabilities ?? {} : {};
|
|
21469
|
+
const autoCommitEnabled = isCapabilityEnabled(completeCaps, "autoCommit");
|
|
21470
|
+
const autoPushEnabled = isCapabilityEnabled(completeCaps, "autoPush");
|
|
21411
21471
|
assertDeployVerification(config2, input, { deployingNow: options.light === true });
|
|
21412
21472
|
const [healthResult, priorCount] = await Promise.all([
|
|
21413
21473
|
adapter2.getCycleHealth().catch(() => ({ totalCycles: 0 })),
|
|
@@ -21682,11 +21742,16 @@ async function completeBuild(adapter2, config2, taskId, input, options = {}, cli
|
|
|
21682
21742
|
}
|
|
21683
21743
|
}
|
|
21684
21744
|
const statusNote = input.completed === "yes" ? options.light ? `Task "${task.title}" (${taskId}) marked Done (light mode \u2014 no review needed).` : `Task "${task.title}" (${taskId}) marked In Review \u2014 ready for your sign-off via \`review_submit\`.` : `Task "${task.title}" (${taskId}) status unchanged (completed: ${input.completed}).`;
|
|
21745
|
+
const dbOnlyMode = hasLocalWorkspace() && (!isGitAvailable() || !isGitRepo(config2.projectRoot));
|
|
21685
21746
|
let commitLine;
|
|
21686
|
-
if (
|
|
21687
|
-
commitLine =
|
|
21688
|
-
} else {
|
|
21747
|
+
if (!autoCommitEnabled) {
|
|
21748
|
+
commitLine = "Auto-commit: skipped (Auto commit capability off).";
|
|
21749
|
+
} else if (!config2.autoCommit) {
|
|
21689
21750
|
commitLine = "Auto-commit: skipped (PAPI_AUTO_COMMIT=false).";
|
|
21751
|
+
} else if (dbOnlyMode) {
|
|
21752
|
+
commitLine = DB_ONLY_COMPLETE_NOTICE;
|
|
21753
|
+
} else {
|
|
21754
|
+
commitLine = autoCommit(config2, taskId, task.title, task.buildHandoff?.filesLikelyTouched);
|
|
21690
21755
|
}
|
|
21691
21756
|
if (isGitAvailable() && isGitRepo(config2.projectRoot)) {
|
|
21692
21757
|
const sha = getHeadCommitSha(config2.projectRoot);
|
|
@@ -21709,6 +21774,9 @@ async function completeBuild(adapter2, config2, taskId, input, options = {}, cli
|
|
|
21709
21774
|
let prLines = [];
|
|
21710
21775
|
if (options.light) {
|
|
21711
21776
|
prLines.push("Light mode: skipping push and PR creation.");
|
|
21777
|
+
} else if (!autoPushEnabled) {
|
|
21778
|
+
prLines.push("Auto push & PR: skipped (Auto push & PR capability off).");
|
|
21779
|
+
} else if (dbOnlyMode) {
|
|
21712
21780
|
} else if (config2.autoCommit && input.completed === "yes") {
|
|
21713
21781
|
prLines = pushAndCreatePR(config2, taskId, task.title, clientName, task.module, cycleNumber);
|
|
21714
21782
|
}
|
|
@@ -21830,8 +21898,10 @@ var OWNER_NAME = process.env["PAPI_OWNER"] ?? "cathalos92";
|
|
|
21830
21898
|
var MODULE_INSTRUCTIONS = {
|
|
21831
21899
|
Dashboard: `**\u26A0\uFE0F MANDATORY \u2014 Dashboard Module Rules (skip = rework)**
|
|
21832
21900
|
|
|
21833
|
-
**STEP 0 \u2014
|
|
21834
|
-
|
|
21901
|
+
**STEP 0 \u2014 If the frontend-design pack is installed, dispatch the design agent for the visual layer (otherwise build it yourself, STEPs 1-4).**
|
|
21902
|
+
This step is conditional \u2014 PAPI does NOT ship the design pack on a hosted/remote connection, so do not assume the agent exists. Check for \`.claude/agents/frontend-design-engineer.md\`:
|
|
21903
|
+
- **Present:** any \`.tsx\` that renders visible UI should be built by the \`frontend-design-engineer\` subagent (dispatch via the Task tool), not inline here. It works in an isolated window loaded with your project's own brand canon (your DESIGN.md / PRODUCT.md), runs design-critique before and after, builds via the \`frontend-design\` / \`impeccable\` skills, self-checks the falsifiable anti-slop blocklist, and verifies in-browser at populated / empty / mobile states. You remain responsible for data, routes, types, and tests \u2014 hand the visual layer to the agent and integrate the report it returns.
|
|
21904
|
+
- **Absent (default on a hosted PAPI harness, and on any project where you haven't opted in):** the pack is opt-in, not required. Install it by running \`setup\` locally on a frontend project (it detects the stack and installs the agent + design-critique skill + guard hook), or copy it from the PAPI server package under \`design-assets/\`. Until then, do NOT dispatch an agent you don't have \u2014 follow STEPs 1-4 below yourself using the generally-available \`frontend-design\` / \`impeccable\` skills.
|
|
21835
21905
|
|
|
21836
21906
|
**STEP 1 \u2014 BEFORE writing any code:**
|
|
21837
21907
|
If you use the \`impeccable\` skill (recommended for dashboard work), it reads two root files for design context: **PRODUCT.md** (strategic \u2014 brand, users, product purpose, design principles) and **DESIGN.md** (visual tokens \u2014 palette, typography, elevation, components). Run \`impeccable init\` to create them if they don't exist yet. Every visual decision must align with these files; if your output contradicts them, it is wrong. (The legacy \`.impeccable.md\` is no longer read by the skill.)
|
|
@@ -21906,6 +21976,7 @@ import { readdirSync as readdirSync6, existsSync as existsSync8, readFileSync as
|
|
|
21906
21976
|
import { join as join12, relative } from "path";
|
|
21907
21977
|
import { homedir as homedir3 } from "os";
|
|
21908
21978
|
import { randomUUID as randomUUID12 } from "crypto";
|
|
21979
|
+
import { docDeletionBlockMessage } from "@papi-ai/shared";
|
|
21909
21980
|
var docRegisterTool = {
|
|
21910
21981
|
name: "doc_register",
|
|
21911
21982
|
description: "Register or update a document in the doc registry. Called after finalising a research/planning doc, or when build_execute detects unregistered docs. Stores metadata and structured summary \u2014 not full content. Re-registering an existing doc updates its summary, tags, actions, type, and status (upsert). Visibility and owner are not changed on re-register.",
|
|
@@ -22300,6 +22371,67 @@ ${userNotes}` : referenceLine;
|
|
|
22300
22371
|
- ${remainingNote}`
|
|
22301
22372
|
);
|
|
22302
22373
|
}
|
|
22374
|
+
var docDeleteTool = {
|
|
22375
|
+
name: "doc_delete",
|
|
22376
|
+
description: "Permanently delete a registered doc. Guarded: ALLOWED only for the doc's creator or the project owner, and only when nothing depends on it \u2014 a delete is BLOCKED if another doc supersedes it, or a task references it (doc_ref) or a linked action. The guard never cascade-deletes dependents; resolve the dependency first. Identify the doc by `doc_path` (preferred) or `doc_id`.",
|
|
22377
|
+
annotations: { title: "Delete Doc", readOnlyHint: false, destructiveHint: true, openWorldHint: false },
|
|
22378
|
+
inputSchema: {
|
|
22379
|
+
type: "object",
|
|
22380
|
+
properties: {
|
|
22381
|
+
doc_path: { type: "string", description: 'Path of the doc to delete (e.g. "docs/research/funding-landscape.md"). Either this or doc_id is required.' },
|
|
22382
|
+
doc_id: { type: "string", description: "UUID of the doc to delete. Either this or doc_path is required." }
|
|
22383
|
+
},
|
|
22384
|
+
required: []
|
|
22385
|
+
}
|
|
22386
|
+
};
|
|
22387
|
+
async function handleDocDelete(adapter2, config2, args) {
|
|
22388
|
+
if (!adapter2.deleteDoc) {
|
|
22389
|
+
return errorResponse("Doc deletion not available \u2014 requires the pg adapter.");
|
|
22390
|
+
}
|
|
22391
|
+
const docPath = args.doc_path?.trim();
|
|
22392
|
+
const docId = args.doc_id?.trim();
|
|
22393
|
+
if (!docPath && !docId) {
|
|
22394
|
+
return errorResponse("Either doc_path or doc_id is required.");
|
|
22395
|
+
}
|
|
22396
|
+
const gate = await resolveOwnerGate(adapter2, config2);
|
|
22397
|
+
const requesterUserId = gate.enforced ? gate.callerUserId : gate.ownerUserId ?? gate.callerUserId;
|
|
22398
|
+
const result = await adapter2.deleteDoc(docId ?? docPath, requesterUserId);
|
|
22399
|
+
if (!result.deleted) {
|
|
22400
|
+
const reason = result.reason ?? "not_authorized";
|
|
22401
|
+
return errorResponse(`Delete blocked \u2014 ${docDeletionBlockMessage(reason)}`);
|
|
22402
|
+
}
|
|
22403
|
+
return textResponse(`**Deleted:** ${docId ?? docPath}`);
|
|
22404
|
+
}
|
|
22405
|
+
var docReorderTool = {
|
|
22406
|
+
name: "doc_reorder",
|
|
22407
|
+
description: "Persist the display order of docs within this project. Pass `doc_ids` as the full desired sequence of doc UUIDs \u2014 each doc's position is saved so the dashboard renders them in that order. Ids not in this project are ignored.",
|
|
22408
|
+
annotations: { title: "Reorder Docs", readOnlyHint: false, destructiveHint: false, openWorldHint: false },
|
|
22409
|
+
inputSchema: {
|
|
22410
|
+
type: "object",
|
|
22411
|
+
properties: {
|
|
22412
|
+
doc_ids: {
|
|
22413
|
+
type: "array",
|
|
22414
|
+
items: { type: "string" },
|
|
22415
|
+
description: "Ordered list of doc UUIDs \u2014 index 0 renders first."
|
|
22416
|
+
}
|
|
22417
|
+
},
|
|
22418
|
+
required: ["doc_ids"]
|
|
22419
|
+
}
|
|
22420
|
+
};
|
|
22421
|
+
async function handleDocReorder(adapter2, args) {
|
|
22422
|
+
if (!adapter2.reorderDocs) {
|
|
22423
|
+
return errorResponse("Doc reorder not available \u2014 requires the pg adapter.");
|
|
22424
|
+
}
|
|
22425
|
+
const docIds = args.doc_ids;
|
|
22426
|
+
if (!Array.isArray(docIds) || docIds.some((id) => typeof id !== "string")) {
|
|
22427
|
+
return errorResponse("doc_ids is required and must be an array of doc UUID strings.");
|
|
22428
|
+
}
|
|
22429
|
+
if (docIds.length === 0) {
|
|
22430
|
+
return errorResponse("doc_ids must contain at least one doc UUID.");
|
|
22431
|
+
}
|
|
22432
|
+
await adapter2.reorderDocs(docIds);
|
|
22433
|
+
return textResponse(`**Reordered ${docIds.length} doc(s).** New order persisted.`);
|
|
22434
|
+
}
|
|
22303
22435
|
|
|
22304
22436
|
// src/tools/build.ts
|
|
22305
22437
|
init_git();
|
|
@@ -22723,7 +22855,8 @@ If this build fixes any of these, pass their UUIDs in \`fixed_issues\` on comple
|
|
|
22723
22855
|
formatModelRecommendation(result.task.buildHandoff?.effort ?? result.task.complexity)
|
|
22724
22856
|
) ?? "";
|
|
22725
22857
|
const gestaltNote = buildGestaltPreBuildDirective(caps) ?? "";
|
|
22726
|
-
|
|
22858
|
+
const buildDisciplineSection = buildPapiMetaFramingDirective(caps, buildDisciplineNote) ?? "";
|
|
22859
|
+
return textResponse(header + serializeBuildHandoff(result.task.buildHandoff) + modelNote + gestaltNote + adSection + moduleInstructions + moduleContext + dogfoodSection + openIssuesSection + verificationNote + buildDisciplineSection + chainInstruction + phaseNote + filesToWriteSection);
|
|
22727
22860
|
} catch (err) {
|
|
22728
22861
|
if (isNoHandoffError(err)) {
|
|
22729
22862
|
const lines = [
|
|
@@ -23461,6 +23594,43 @@ This ${type} is visible to PAPI maintainers.${followUpLine}${autoRouteLine}`
|
|
|
23461
23594
|
const truncateWarning = notesTruncated ? ` (notes truncated to ${MAX_NOTES_LENGTH} chars)` : "";
|
|
23462
23595
|
return textResponse(`\u{1F41B} ${task.id}: "${task.title}" \u2014 ${severityLabel} bug added to backlog${overrideNote}${branchNote}. Will be picked up by next plan.${truncateWarning}`);
|
|
23463
23596
|
}
|
|
23597
|
+
var bugListTool = {
|
|
23598
|
+
name: "bug_list",
|
|
23599
|
+
description: "List the upstream bug/idea reports YOU filed to the PAPI maintainers (via the `bug` tool with report=true or an auto-routed PAPI bug), newest first. Read-only. Shows each report's id, kind (bug/idea), triage status, description, and when it was filed. Scoped to your own submissions only \u2014 never another user's. Requires a database adapter (pg or hosted proxy); the local md adapter has no upstream store. Does not call the Anthropic API.",
|
|
23600
|
+
annotations: { title: "List My Bug Reports", readOnlyHint: true, destructiveHint: false, openWorldHint: false },
|
|
23601
|
+
inputSchema: {
|
|
23602
|
+
type: "object",
|
|
23603
|
+
properties: {
|
|
23604
|
+
limit: {
|
|
23605
|
+
type: "integer",
|
|
23606
|
+
description: "Optional maximum number of reports to return. Omit to return all. Reserved for future pagination \u2014 current behaviour returns all your reports newest-first regardless of value."
|
|
23607
|
+
}
|
|
23608
|
+
},
|
|
23609
|
+
required: []
|
|
23610
|
+
}
|
|
23611
|
+
};
|
|
23612
|
+
async function handleBugList(adapter2, config2) {
|
|
23613
|
+
if (!adapter2.listMyBugReports) {
|
|
23614
|
+
return errorResponse(
|
|
23615
|
+
"Listing your bug reports requires a database adapter (pg or hosted proxy). The md adapter has no upstream report store."
|
|
23616
|
+
);
|
|
23617
|
+
}
|
|
23618
|
+
const reports = await adapter2.listMyBugReports(config2.userId);
|
|
23619
|
+
if (reports.length === 0) {
|
|
23620
|
+
return textResponse("You haven't filed any bug or idea reports yet. Use the `bug` tool to report a PAPI bug or suggest an idea.");
|
|
23621
|
+
}
|
|
23622
|
+
const lines = reports.map((r) => {
|
|
23623
|
+
const kind = r.type === "idea" ? "\u{1F4A1} idea" : "\u{1F41B} bug";
|
|
23624
|
+
const when = r.createdAt.slice(0, 10);
|
|
23625
|
+
return `- \`${r.id}\` \xB7 ${kind} \xB7 ${r.status} \xB7 ${when}
|
|
23626
|
+
${r.description}`;
|
|
23627
|
+
});
|
|
23628
|
+
return textResponse(
|
|
23629
|
+
`**Your reports (${reports.length}, newest first)**
|
|
23630
|
+
|
|
23631
|
+
${lines.join("\n")}`
|
|
23632
|
+
);
|
|
23633
|
+
}
|
|
23464
23634
|
|
|
23465
23635
|
// src/tools/ad-hoc.ts
|
|
23466
23636
|
init_git();
|
|
@@ -28640,6 +28810,7 @@ async function handleDiscoveredIssueResolve(adapter2, args) {
|
|
|
28640
28810
|
import path6 from "path";
|
|
28641
28811
|
|
|
28642
28812
|
// src/services/entitlements.ts
|
|
28813
|
+
import { evaluateContributorGate } from "@papi-ai/shared";
|
|
28643
28814
|
var FREE_PROJECT_CAP = 3;
|
|
28644
28815
|
var PAID_TIERS = /* @__PURE__ */ new Set(["pro", "team"]);
|
|
28645
28816
|
var PRICING_URL = "https://getpapi.ai/pricing";
|
|
@@ -28672,11 +28843,9 @@ async function enforceProjectCap(adapter2, target) {
|
|
|
28672
28843
|
if (projects.length >= FREE_PROJECT_CAP) return projectCapMessage(projects.length);
|
|
28673
28844
|
return null;
|
|
28674
28845
|
}
|
|
28675
|
-
async function
|
|
28846
|
+
async function resolveContributorUpsell(adapter2) {
|
|
28676
28847
|
const tier = await resolveTier(adapter2);
|
|
28677
|
-
|
|
28678
|
-
if (tier === "team") return null;
|
|
28679
|
-
return contributorTeamMessage(tier);
|
|
28848
|
+
return evaluateContributorGate(tier).upsell ?? null;
|
|
28680
28849
|
}
|
|
28681
28850
|
function projectCapMessage(currentCount) {
|
|
28682
28851
|
return `**You're on the Free plan (${currentCount} of ${FREE_PROJECT_CAP} projects).**
|
|
@@ -28685,11 +28854,6 @@ Free covers up to ${FREE_PROJECT_CAP} projects. To run more, upgrade to Pro for
|
|
|
28685
28854
|
|
|
28686
28855
|
Your existing projects are untouched, and you can keep working in any of them.`;
|
|
28687
28856
|
}
|
|
28688
|
-
function contributorTeamMessage(tier) {
|
|
28689
|
-
return `**Adding people to a project is a Team feature.**
|
|
28690
|
-
|
|
28691
|
-
You're on the ${tier} plan. Shared projects, roles, and the Quality Gate come with Team. Read-only viewer seats are free, so you only pay for who is actually building: ${PRICING_URL}`;
|
|
28692
|
-
}
|
|
28693
28857
|
|
|
28694
28858
|
// src/tools/project.ts
|
|
28695
28859
|
function workspacePapiDir(config2) {
|
|
@@ -28883,17 +29047,19 @@ function requireEmail(args) {
|
|
|
28883
29047
|
async function handleContributorAdd(adapter2, config2, args) {
|
|
28884
29048
|
const denied = await denyUnlessOwner(adapter2, config2);
|
|
28885
29049
|
if (denied) return errorResponse(denied);
|
|
28886
|
-
const tierDenied = await enforceContributorGate(adapter2);
|
|
28887
|
-
if (tierDenied) return errorResponse(tierDenied);
|
|
28888
29050
|
const email = requireEmail(args);
|
|
28889
29051
|
if (!email) return errorResponse('A valid email is required. Example: contributor_add email="wes@example.com"');
|
|
28890
29052
|
try {
|
|
28891
29053
|
const entry = await adapter2.addContributorByEmail(email);
|
|
28892
29054
|
const name = entry.displayName ? ` (${entry.displayName})` : "";
|
|
29055
|
+
const upsell = await resolveContributorUpsell(adapter2);
|
|
29056
|
+
const upsellSuffix = upsell ? `
|
|
29057
|
+
|
|
29058
|
+
${upsell}` : "";
|
|
28893
29059
|
return textResponse(
|
|
28894
29060
|
`\u2705 Added **${entry.email ?? email}**${name} as a contributor.
|
|
28895
29061
|
|
|
28896
|
-
They now have contributors-tier visibility on this project. Roles and invites land with MU-2 \u2014 today membership is the whole model.`
|
|
29062
|
+
They now have contributors-tier visibility on this project. Roles and invites land with MU-2 \u2014 today membership is the whole model.` + upsellSuffix
|
|
28897
29063
|
);
|
|
28898
29064
|
} catch (err) {
|
|
28899
29065
|
return errorResponse(err instanceof Error ? err.message : String(err));
|
|
@@ -29636,6 +29802,7 @@ var PAPI_TOOLS = [
|
|
|
29636
29802
|
buildCancelTool,
|
|
29637
29803
|
ideaTool,
|
|
29638
29804
|
bugTool,
|
|
29805
|
+
bugListTool,
|
|
29639
29806
|
adHocTool,
|
|
29640
29807
|
boardReconcileTool,
|
|
29641
29808
|
releaseTool,
|
|
@@ -29651,6 +29818,8 @@ var PAPI_TOOLS = [
|
|
|
29651
29818
|
docSearchTool,
|
|
29652
29819
|
docScanTool,
|
|
29653
29820
|
docActionPromoteTool,
|
|
29821
|
+
docDeleteTool,
|
|
29822
|
+
docReorderTool,
|
|
29654
29823
|
getSiblingAdsTool,
|
|
29655
29824
|
handoffGenerateTool,
|
|
29656
29825
|
scopeBriefTool,
|
|
@@ -29816,6 +29985,8 @@ function createServer(adapter2, config2) {
|
|
|
29816
29985
|
return handleIdea(adapter2, config2, safeArgs);
|
|
29817
29986
|
case "bug":
|
|
29818
29987
|
return handleBug(adapter2, config2, safeArgs);
|
|
29988
|
+
case "bug_list":
|
|
29989
|
+
return handleBugList(adapter2, config2);
|
|
29819
29990
|
case "ad_hoc":
|
|
29820
29991
|
return handleAdHoc(adapter2, config2, safeArgs);
|
|
29821
29992
|
case "board_reconcile":
|
|
@@ -29847,6 +30018,10 @@ function createServer(adapter2, config2) {
|
|
|
29847
30018
|
return handleDocScan(adapter2, config2, safeArgs);
|
|
29848
30019
|
case "doc_action_promote":
|
|
29849
30020
|
return handleDocActionPromote(adapter2, safeArgs);
|
|
30021
|
+
case "doc_delete":
|
|
30022
|
+
return handleDocDelete(adapter2, config2, safeArgs);
|
|
30023
|
+
case "doc_reorder":
|
|
30024
|
+
return handleDocReorder(adapter2, safeArgs);
|
|
29850
30025
|
case "get_sibling_ads":
|
|
29851
30026
|
return handleGetSiblingAds(adapter2, safeArgs);
|
|
29852
30027
|
case "handoff_generate":
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@papi-ai/server",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.66",
|
|
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",
|