@papi-ai/server 0.7.64 → 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.
@@ -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 (config2.autoCommit) {
21687
- commitLine = autoCommit(config2, taskId, task.title, task.buildHandoff?.filesLikelyTouched);
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 Dispatch the design agent for the visual layer (do NOT hand-write UI in the main build context).**
21834
- Any \`.tsx\` that renders visible UI must be built by the \`frontend-design-engineer\` subagent (dispatch via the Task tool), not inline here. It works in an isolated window loaded only with brand canon (DESIGN.md / PRODUCT.md / brand-book), 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. Components hand-written in this context produce generic output that fails review. (If \`.claude/agents/frontend-design-engineer.md\` is absent \u2014 e.g. a fresh external install \u2014 run STEPs 1-4 below yourself.)
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
- return textResponse(header + serializeBuildHandoff(result.task.buildHandoff) + modelNote + gestaltNote + adSection + moduleInstructions + moduleContext + dogfoodSection + openIssuesSection + verificationNote + buildDisciplineNote + chainInstruction + phaseNote + filesToWriteSection);
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 enforceContributorGate(adapter2) {
28846
+ async function resolveContributorUpsell(adapter2) {
28676
28847
  const tier = await resolveTier(adapter2);
28677
- if (tier === null) return null;
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.64",
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",