@papi-ai/server 0.7.51 → 0.7.52

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.
Files changed (2) hide show
  1. package/dist/index.js +181 -51
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -13096,7 +13096,7 @@ var planPrepareCache = new PerCallerCache();
13096
13096
  var planTool = {
13097
13097
  name: "plan",
13098
13098
  description: 'Run once per cycle to select tasks and generate BUILD HANDOFFs. Call after setup (first time) or after completing all builds AND running release for the previous cycle. Returns prioritised task recommendations with detailed implementation specs. NEVER call when unbuilt cycle tasks exist \u2014 build and release first. First call returns a planning prompt for you to execute (prepare phase). Then call again with mode "apply" and your output to write results. Use skip_handoffs=true for large backlogs \u2014 handoffs are then generated separately via `handoff_generate`.',
13099
- annotations: { readOnlyHint: false, destructiveHint: false },
13099
+ annotations: { title: "Plan Cycle", readOnlyHint: false, destructiveHint: false, openWorldHint: false },
13100
13100
  inputSchema: {
13101
13101
  type: "object",
13102
13102
  properties: {
@@ -13641,15 +13641,34 @@ async function findSimilarTasks(adapter2, ideaTitle) {
13641
13641
  return matches.sort((a, b2) => b2.coverage - a.coverage).slice(0, 3);
13642
13642
  }
13643
13643
  var AD_ALIGNMENT_OVERLAP_THRESHOLD = 3;
13644
+ var DOC_FREQ_CEILING = 0.35;
13645
+ function findUbiquitousWords(ads) {
13646
+ const live = ads.filter((a) => !a.superseded);
13647
+ const ubiquitous = /* @__PURE__ */ new Set();
13648
+ if (live.length < 5) return ubiquitous;
13649
+ const docFreq = /* @__PURE__ */ new Map();
13650
+ for (const ad of live) {
13651
+ for (const w of extractKeywords(`${ad.title} ${ad.body}`)) {
13652
+ docFreq.set(w, (docFreq.get(w) ?? 0) + 1);
13653
+ }
13654
+ }
13655
+ const ceiling = live.length * DOC_FREQ_CEILING;
13656
+ for (const [w, df] of docFreq) {
13657
+ if (df > ceiling) ubiquitous.add(w);
13658
+ }
13659
+ return ubiquitous;
13660
+ }
13644
13661
  function findAdAlignmentMatches(ideaText, ads) {
13645
13662
  const ideaKeywords = extractKeywords(ideaText);
13646
13663
  if (ideaKeywords.size < AD_ALIGNMENT_OVERLAP_THRESHOLD) return [];
13664
+ const ubiquitous = findUbiquitousWords(ads);
13647
13665
  const matches = [];
13648
13666
  for (const ad of ads) {
13649
13667
  if (ad.superseded) continue;
13650
13668
  const adKeywords = extractKeywords(`${ad.title} ${ad.body}`);
13651
13669
  let overlap = 0;
13652
13670
  for (const w of ideaKeywords) {
13671
+ if (ubiquitous.has(w)) continue;
13653
13672
  if (adKeywords.has(w)) overlap++;
13654
13673
  }
13655
13674
  if (overlap >= AD_ALIGNMENT_OVERLAP_THRESHOLD) {
@@ -15494,7 +15513,7 @@ var reviewPrepareCache = new PerCallerCache();
15494
15513
  var strategyReviewTool = {
15495
15514
  name: "strategy_review",
15496
15515
  description: 'Run a Strategy Review \u2014 assesses project direction, velocity, and Active Decisions. Produces recommendations and potential AD updates that feed into the next plan. Offered every 5 cycles; hard-blocked at 7+ overdue cycles. Run it in your current conversation \u2014 only start a fresh one if you are genuinely under context pressure (your host just compacted, you are near the context limit, or the session is heavy with build context), not just because a review is next. First call returns a review prompt for you to execute (prepare phase). Then call again with mode "apply" and your output. Pass `force: true` to run before the cadence gate.',
15497
- annotations: { readOnlyHint: false, destructiveHint: false },
15516
+ annotations: { title: "Run Strategy Review", readOnlyHint: false, destructiveHint: false, openWorldHint: false },
15498
15517
  inputSchema: {
15499
15518
  type: "object",
15500
15519
  properties: {
@@ -15531,7 +15550,7 @@ var strategyReviewTool = {
15531
15550
  var strategyAgendaTool = {
15532
15551
  name: "strategy_agenda",
15533
15552
  description: 'Queue topics for the next strategy review. Topics surface as input in the next `strategy_review` prepare phase and are automatically marked as addressed after the review completes. Two modes: "add" to queue a topic, "list" to see pending topics. Use this when you spot a strategic question during a build \u2014 capture the topic now instead of losing it.',
15534
- annotations: { readOnlyHint: false, destructiveHint: false },
15553
+ annotations: { title: "Strategy Agenda", readOnlyHint: false, destructiveHint: false, openWorldHint: false },
15535
15554
  inputSchema: {
15536
15555
  type: "object",
15537
15556
  properties: {
@@ -15559,7 +15578,7 @@ var strategyAgendaTool = {
15559
15578
  var strategyChangeTool = {
15560
15579
  name: "strategy_change",
15561
15580
  description: 'Apply a strategic shift to the project. Three modes: "capture" for lightweight mid-conversation decision capture (no LLM round-trip), "prepare" to get a change prompt for full analysis, "apply" to persist analysis output. Use "capture" when you detect a strategic decision in conversation and want to persist it quickly without disrupting the build flow. In "capture" mode, pass north_star to directly set/update the project North Star (no decision text needed).',
15562
- annotations: { readOnlyHint: false, destructiveHint: false },
15581
+ annotations: { title: "Change Strategy", readOnlyHint: false, destructiveHint: false, openWorldHint: false },
15563
15582
  inputSchema: {
15564
15583
  type: "object",
15565
15584
  properties: {
@@ -16018,7 +16037,7 @@ async function archiveTasks(adapter2, phases, statuses) {
16018
16037
  var boardViewTool = {
16019
16038
  name: "board_view",
16020
16039
  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.',
16021
- annotations: { readOnlyHint: true, destructiveHint: false },
16040
+ annotations: { title: "View Board", readOnlyHint: true, destructiveHint: false, openWorldHint: false },
16022
16041
  inputSchema: {
16023
16042
  type: "object",
16024
16043
  properties: {
@@ -16062,7 +16081,7 @@ var boardViewTool = {
16062
16081
  var boardDeprioritiseTool = {
16063
16082
  name: "board_deprioritise",
16064
16083
  description: `Remove a task from the current cycle. Four actions: "backlog" (not now, maybe later \u2014 preserves handoff), "defer" (valid but premature \u2014 hidden from planner), "block" (waiting on external dependency \u2014 visible on board but skipped by planner), "cancel" (don't want this \u2014 permanently closed with reason). When a user rejects a task, ALWAYS ask which action they want. Does not call the Anthropic API.`,
16065
- annotations: { readOnlyHint: false, destructiveHint: true },
16084
+ annotations: { title: "Deprioritise Task", readOnlyHint: false, destructiveHint: true, openWorldHint: false },
16066
16085
  inputSchema: {
16067
16086
  type: "object",
16068
16087
  properties: {
@@ -16107,7 +16126,7 @@ var boardDeprioritiseTool = {
16107
16126
  var boardArchiveTool = {
16108
16127
  name: "board_archive",
16109
16128
  description: "Archive tasks from the Board to the archive file. When both phase and status are provided, only tasks matching BOTH are archived (AND logic). When only one is provided, all matching tasks are archived. Does not call the Anthropic API.",
16110
- annotations: { readOnlyHint: false, destructiveHint: true },
16129
+ annotations: { title: "Archive Task", readOnlyHint: false, destructiveHint: true, openWorldHint: false },
16111
16130
  inputSchema: {
16112
16131
  type: "object",
16113
16132
  properties: {
@@ -16126,7 +16145,7 @@ var boardArchiveTool = {
16126
16145
  var boardEditTool = {
16127
16146
  name: "board_edit",
16128
16147
  description: "Edit fields on an existing task. Supports title, priority, complexity, module, epic, phase, notes (with notes_mode for append/replace/clear), status, maturity, and cycle (number or null). Pass task_id plus any fields to update. Does not call the Anthropic API.",
16129
- annotations: { readOnlyHint: false, destructiveHint: false },
16148
+ annotations: { title: "Edit Task", readOnlyHint: false, destructiveHint: false, openWorldHint: false },
16130
16149
  inputSchema: {
16131
16150
  type: "object",
16132
16151
  properties: {
@@ -18069,7 +18088,7 @@ async function ensureMcpJsonGitignored(projectRoot) {
18069
18088
  var setupTool = {
18070
18089
  name: "setup",
18071
18090
  description: 'Create a new PAPI-tracked project \u2014 generates your Product Brief, Active Decisions, and CLAUDE.md workflow instructions. Run after configuring your MCP credentials (via `init` or manually from getpapi.ai). Only project_name is required \u2014 description and target_users are derived from README, package.json, and commit history when omitted. Set existing_project: true to adopt an existing codebase. ADOPTING AN EXISTING PROJECT OVER A REMOTE/HOSTED CONNECTOR (no local stdio install): PAPI cannot read your filesystem, so YOU (the client) must gather a `codebase_scan` and pass it in \u2014 list top-level dirs/files, the package manifest, the README (first ~3000 chars), and recent commit subjects. Without it, adoption falls back to asking for description/target_users. On a local stdio install PAPI scans the tree itself, so `codebase_scan` is optional there. First call returns prompts (prepare phase), then call again with mode "apply" and your outputs. After setup, run `plan` to start your first cycle.',
18072
- annotations: { readOnlyHint: false, destructiveHint: false },
18091
+ annotations: { title: "Set Up Project", readOnlyHint: false, destructiveHint: false, openWorldHint: false },
18073
18092
  inputSchema: {
18074
18093
  type: "object",
18075
18094
  properties: {
@@ -19354,7 +19373,7 @@ async function postReleaseToX(version, cycleClosed) {
19354
19373
  var releaseTool = {
19355
19374
  name: "release",
19356
19375
  description: "Cut a versioned release \u2014 creates a git tag, generates CHANGELOG.md, and pushes to remote. Pass skipVersion=true to update CHANGELOG and close the cycle without creating a tag or bumping version numbers.",
19357
- annotations: { readOnlyHint: false, destructiveHint: true },
19376
+ annotations: { title: "Cut Release", readOnlyHint: false, destructiveHint: true, openWorldHint: false },
19358
19377
  inputSchema: {
19359
19378
  type: "object",
19360
19379
  properties: {
@@ -20591,15 +20610,16 @@ async function completeBuild(adapter2, config2, taskId, input, options = {}, cli
20591
20610
  const adIds = report.relatedDecisions ?? [];
20592
20611
  const extractLearning = (text, category) => {
20593
20612
  if (!text || text === "None" || text.trim().length === 0) return;
20594
- const dotIdx = text.indexOf(". ");
20595
- const summary = dotIdx > 0 && dotIdx < 120 ? text.slice(0, dotIdx + 1) : text.slice(0, 120);
20596
- const tags = [];
20597
- if (taskModule) tags.push(taskModule.toLowerCase());
20598
20613
  let severity;
20599
20614
  if (category === "issue") {
20600
20615
  const sevMatch = text.match(/^(P[0-3])[\s:]/);
20601
20616
  if (sevMatch) severity = sevMatch[1];
20602
20617
  }
20618
+ const body = severity ? text.replace(/^P[0-3][\s:]+/, "").trim() : text.trim();
20619
+ const dotIdx = body.indexOf(". ");
20620
+ const summary = dotIdx > 0 ? body.slice(0, dotIdx + 1) : body;
20621
+ const tags = [];
20622
+ if (taskModule) tags.push(taskModule.toLowerCase());
20603
20623
  learnings.push({
20604
20624
  taskId: task.id,
20605
20625
  cycleNumber,
@@ -21000,7 +21020,7 @@ import { randomUUID as randomUUID12 } from "crypto";
21000
21020
  var docRegisterTool = {
21001
21021
  name: "doc_register",
21002
21022
  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.",
21003
- annotations: { readOnlyHint: false, destructiveHint: false },
21023
+ annotations: { title: "Register Doc", readOnlyHint: false, destructiveHint: false, openWorldHint: false },
21004
21024
  inputSchema: {
21005
21025
  type: "object",
21006
21026
  properties: {
@@ -21033,7 +21053,7 @@ var docRegisterTool = {
21033
21053
  var docSearchTool = {
21034
21054
  name: "doc_search",
21035
21055
  description: "Search the doc registry for documents by type, tags, keyword, or pending actions. Returns summaries, not full content. Use for context gathering in plan, strategy review, and idea dedup.",
21036
- annotations: { readOnlyHint: true, destructiveHint: false },
21056
+ annotations: { title: "Search Docs", readOnlyHint: true, destructiveHint: false, openWorldHint: false },
21037
21057
  inputSchema: {
21038
21058
  type: "object",
21039
21059
  properties: {
@@ -21051,7 +21071,7 @@ var docSearchTool = {
21051
21071
  var docScanTool = {
21052
21072
  name: "doc_scan",
21053
21073
  description: "Scan docs/ and plans directories for unregistered .md files. Returns a list of files not yet in the doc registry. Use this to find docs that need registration.",
21054
- annotations: { readOnlyHint: true, destructiveHint: false },
21074
+ annotations: { title: "Scan Docs", readOnlyHint: true, destructiveHint: false, openWorldHint: false },
21055
21075
  inputSchema: {
21056
21076
  type: "object",
21057
21077
  properties: {
@@ -21298,7 +21318,7 @@ async function handleDocScan(adapter2, config2, args) {
21298
21318
  var docActionPromoteTool = {
21299
21319
  name: "doc_action_promote",
21300
21320
  description: "Promote a single pending action from a registered doc into a Backlog task. The new task gets a `Reference:` line pointing to the source doc, and the doc action is marked resolved with `linkedTaskId` set. Use to close the research-to-action loop \u2014 turn unactioned findings into trackable cycle work. Identify the doc by `doc_path` (preferred) or `doc_id`, and the action by 0-based `action_index` (as listed in `doc_search` output).",
21301
- annotations: { readOnlyHint: false, destructiveHint: false },
21321
+ annotations: { title: "Promote Doc Action", readOnlyHint: false, destructiveHint: false, openWorldHint: false },
21302
21322
  inputSchema: {
21303
21323
  type: "object",
21304
21324
  properties: {
@@ -21397,7 +21417,7 @@ init_git();
21397
21417
  var buildListTool = {
21398
21418
  name: "build_list",
21399
21419
  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.",
21400
- annotations: { readOnlyHint: true, destructiveHint: false },
21420
+ annotations: { title: "List Builds", readOnlyHint: true, destructiveHint: false, openWorldHint: false },
21401
21421
  inputSchema: {
21402
21422
  type: "object",
21403
21423
  properties: {
@@ -21412,7 +21432,7 @@ var buildListTool = {
21412
21432
  var buildDescribeTool = {
21413
21433
  name: "build_describe",
21414
21434
  description: "Show the full BUILD HANDOFF for a specific task, including scope, acceptance criteria, and implementation guidance. Does not call the Anthropic API.",
21415
- annotations: { readOnlyHint: true, destructiveHint: false },
21435
+ annotations: { title: "Describe Build", readOnlyHint: true, destructiveHint: false, openWorldHint: false },
21416
21436
  inputSchema: {
21417
21437
  type: "object",
21418
21438
  properties: {
@@ -21427,7 +21447,7 @@ var buildDescribeTool = {
21427
21447
  var buildExecuteTool = {
21428
21448
  name: "build_execute",
21429
21449
  description: "Start or complete a build task. Call with just task_id to start (returns BUILD HANDOFF, creates feature branch, marks In Progress). After implementing the task, you MUST call build_execute again with all report fields (completed, effort, estimated_effort, surprises, discovered_issues, architecture_notes) to finish \u2014 do not wait for user confirmation between start and complete. Never call on tasks that are already In Review or Done. Does not call the Anthropic API. Set light=true to skip branch/PR creation (commits to current branch). Set PAPI_LIGHT_MODE=true in env to default all builds to light mode.",
21430
- annotations: { readOnlyHint: false, destructiveHint: false },
21450
+ annotations: { title: "Run Build", readOnlyHint: false, destructiveHint: false, openWorldHint: false },
21431
21451
  inputSchema: {
21432
21452
  type: "object",
21433
21453
  properties: {
@@ -21541,7 +21561,7 @@ var buildExecuteTool = {
21541
21561
  var buildCancelTool = {
21542
21562
  name: "build_cancel",
21543
21563
  description: "Cancel a build task with a reason. Sets the task status to Cancelled and records the closure reason. Does not call the Anthropic API.",
21544
- annotations: { readOnlyHint: false, destructiveHint: true },
21564
+ annotations: { title: "Cancel Build", readOnlyHint: false, destructiveHint: true, openWorldHint: false },
21545
21565
  inputSchema: {
21546
21566
  type: "object",
21547
21567
  properties: {
@@ -22089,7 +22109,7 @@ Reason: ${result.reason}`);
22089
22109
  init_git();
22090
22110
  var ideaTool = {
22091
22111
  name: "idea",
22092
- annotations: { readOnlyHint: false, destructiveHint: false },
22112
+ annotations: { title: "Capture Idea", readOnlyHint: false, destructiveHint: false, openWorldHint: false },
22093
22113
  description: "Capture an idea as a Backlog task. The next plan run will triage and scope it. Use anytime to log bugs, feature requests, or improvements without interrupting the current cycle. IMPORTANT: If this idea originates from a research or planning session, you MUST include a Reference: line in notes pointing to the source doc. Without it, the planner has no context and will misinterpret the intent. Does not call the Anthropic API.",
22094
22114
  inputSchema: {
22095
22115
  type: "object",
@@ -22306,7 +22326,7 @@ function collectDiagnostics(config2) {
22306
22326
  var bugTool = {
22307
22327
  name: "bug",
22308
22328
  description: `Report a bug OR submit an idea. Routing: a bug about PAPI itself (a PAPI tool/MCP error, the connector, a handoff/cycle problem) auto-submits UPSTREAM to PAPI maintainers with diagnostics \u2014 you do NOT need to set report=true for these. A bug in the user's OWN project auto-files as a Backlog task on their board. IMPORTANT: report=true is ONLY for a genuine PAPI-product defect \u2014 it is NOT the catch-all for "not about my app". A bug in the user's harness/editor/OS/git/other tooling (e.g. Claude Code, Codex, VS Code, a shell command) is NOT a PAPI bug: file it on the user's own board (report=false / default) or that tool's own tracker, never upstream. Override the routing explicitly: report=true forces upstream (PAPI-product only), report=false forces the user's own board. Set \`type\` ("bug"/"idea") and optional notify-when-fixed / contact-ok consent for upstream submissions. Does not call the Anthropic API.`,
22309
- annotations: { readOnlyHint: false, destructiveHint: false },
22329
+ annotations: { title: "Report Bug", readOnlyHint: false, destructiveHint: false, openWorldHint: false },
22310
22330
  inputSchema: {
22311
22331
  type: "object",
22312
22332
  properties: {
@@ -22563,7 +22583,7 @@ function inferTaskType(description) {
22563
22583
  var adHocTool = {
22564
22584
  name: "ad_hoc",
22565
22585
  description: "Record work done outside the normal cycle. Creates a Done task with a lightweight build report, or associates work with an existing task if task_id is provided (without changing task status \u2014 use build_execute for status transitions). Use for quick fixes, bug patches, or ad-hoc changes. Does not call the Anthropic API.",
22566
- annotations: { readOnlyHint: false, destructiveHint: false },
22586
+ annotations: { title: "Record Ad-Hoc Work", readOnlyHint: false, destructiveHint: false, openWorldHint: false },
22567
22587
  inputSchema: {
22568
22588
  type: "object",
22569
22589
  properties: {
@@ -23053,7 +23073,7 @@ async function applyReconcile(adapter2, corrections) {
23053
23073
  var boardReconcileTool = {
23054
23074
  name: "board_reconcile",
23055
23075
  description: 'Holistic board review \u2014 backlog + deferred tasks in one pass. Surfaces strategic context (ADs, phases, docs), grouping signals, merge candidates, priority drift, and stale tasks. "prepare" assembles context for you to analyse; "apply" commits your decisions after user confirmation. Does not call the Anthropic API.',
23056
- annotations: { readOnlyHint: false, destructiveHint: false },
23076
+ annotations: { title: "Reconcile Board", readOnlyHint: false, destructiveHint: false, openWorldHint: false },
23057
23077
  inputSchema: {
23058
23078
  type: "object",
23059
23079
  properties: {
@@ -23546,7 +23566,7 @@ ${diffBlock}`;
23546
23566
  var reviewListTool = {
23547
23567
  name: "review_list",
23548
23568
  description: "List tasks ready for your sign-off \u2014 shows completed builds waiting for approval or feedback. Does not call the Anthropic API.",
23549
- annotations: { readOnlyHint: true, destructiveHint: false },
23569
+ annotations: { title: "List Reviews", readOnlyHint: true, destructiveHint: false, openWorldHint: false },
23550
23570
  inputSchema: {
23551
23571
  type: "object",
23552
23572
  properties: {
@@ -23561,7 +23581,7 @@ var reviewListTool = {
23561
23581
  var reviewSubmitTool = {
23562
23582
  name: "review_submit",
23563
23583
  description: "Record a review verdict on a completed build (build-acceptance) or task plan (handoff-review). ALWAYS ask the human for their verdict before calling \u2014 never auto-submit without human input. Accept moves the task to Done, request-changes sends it back for rework, reject discards the build. Updates task status based on the verdict. On handoff-review with suggested changes, returns a prompt to revise the BUILD HANDOFF.\n\nDO NOT use this tool as a substitute for review_list. If you need to see what is pending review, call review_list first. If review_list is unavailable in your tool set, STOP and tell the human their MCP integration is incomplete rather than guessing at the next pending task. (SUP-2026-010.)",
23564
- annotations: { readOnlyHint: false, destructiveHint: false },
23584
+ annotations: { title: "Submit Review", readOnlyHint: false, destructiveHint: false, openWorldHint: false },
23565
23585
  inputSchema: {
23566
23586
  type: "object",
23567
23587
  properties: {
@@ -24113,7 +24133,7 @@ ${statusNote}${autoReviewNote}${securityNote}${unblockNote}${docClosureNote}${re
24113
24133
  var reviewClaimTool = {
24114
24134
  name: "review_claim",
24115
24135
  description: "Claim a Pending Review from the shared cross-user review queue so you are the one reviewing it. Atomic first-claim-wins \u2014 two reviewers cannot grab the same build. After claiming, run review_submit to record your verdict. Owner-or-active-member only. Does not call the Anthropic API.",
24116
- annotations: { readOnlyHint: false, destructiveHint: false },
24136
+ annotations: { title: "Claim Review", readOnlyHint: false, destructiveHint: false, openWorldHint: false },
24117
24137
  inputSchema: {
24118
24138
  type: "object",
24119
24139
  properties: {
@@ -24178,7 +24198,7 @@ import path5 from "path";
24178
24198
  var initTool = {
24179
24199
  name: "init",
24180
24200
  description: "Write the MCP config file that connects this project to PAPI. Generates .mcp.json (Claude Code default) or the equivalent for Cursor, VS Code, Windsurf, OpenCode, Amazon Q, Kilo Code, Gemini CLI, Codex CLI, or Hermes Agent. Config-only \u2014 does not create any project data. Run this first, then run `setup` to create your PAPI project.",
24181
- annotations: { readOnlyHint: false, destructiveHint: false },
24201
+ annotations: { title: "Initialise Project", readOnlyHint: false, destructiveHint: false, openWorldHint: false },
24182
24202
  inputSchema: {
24183
24203
  type: "object",
24184
24204
  properties: {
@@ -25087,6 +25107,104 @@ function formatUnblockSection(candidates) {
25087
25107
  return lines.join("\n");
25088
25108
  }
25089
25109
 
25110
+ // src/lib/deferred-gate.ts
25111
+ var GATE_PHRASES = [
25112
+ "depends on",
25113
+ "depends upon",
25114
+ "gated on",
25115
+ "gated behind",
25116
+ "blocked on",
25117
+ "blocked by",
25118
+ "waiting on",
25119
+ "waiting for",
25120
+ "pending",
25121
+ "once",
25122
+ "after",
25123
+ "until",
25124
+ "requires",
25125
+ "needs",
25126
+ "pull condition",
25127
+ "unblocked by"
25128
+ ];
25129
+ var CONTEXT_WINDOW = 60;
25130
+ var TASK_REF2 = /\btask-\d+\b/gi;
25131
+ var MAX_PER_BUCKET = 8;
25132
+ function extractGatedTaskRefs(notes) {
25133
+ if (!notes) return [];
25134
+ const out = [];
25135
+ const seen = /* @__PURE__ */ new Set();
25136
+ for (const m of notes.matchAll(TASK_REF2)) {
25137
+ const ref = m[0].toLowerCase();
25138
+ if (seen.has(ref)) continue;
25139
+ const start = Math.max(0, (m.index ?? 0) - CONTEXT_WINDOW);
25140
+ const before = notes.slice(start, m.index).toLowerCase();
25141
+ const hit = GATE_PHRASES.find((p) => before.includes(p));
25142
+ if (!hit) continue;
25143
+ seen.add(ref);
25144
+ const evidence = notes.slice(start, (m.index ?? 0) + m[0].length).trim();
25145
+ out.push({ ref, evidence: evidence.length > 90 ? `\u2026${evidence.slice(-90)}` : evidence });
25146
+ }
25147
+ return out;
25148
+ }
25149
+ async function findDeferredGates(adapter2) {
25150
+ const empty = { unblock: [], zombie: [] };
25151
+ let allTasks = [];
25152
+ try {
25153
+ allTasks = await adapter2.queryBoard();
25154
+ } catch {
25155
+ return empty;
25156
+ }
25157
+ const deferred = allTasks.filter((t) => t.status === "Deferred");
25158
+ if (deferred.length === 0) return empty;
25159
+ const byId = /* @__PURE__ */ new Map();
25160
+ for (const t of allTasks) {
25161
+ if (t.displayId) byId.set(t.displayId.toLowerCase(), t);
25162
+ }
25163
+ const sweep = { unblock: [], zombie: [] };
25164
+ for (const task of deferred) {
25165
+ for (const { ref, evidence } of extractGatedTaskRefs(task.notes ?? "")) {
25166
+ const self = task.displayId?.toLowerCase();
25167
+ if (self && ref === self) continue;
25168
+ const upstream = byId.get(ref);
25169
+ if (!upstream) continue;
25170
+ if (upstream.status !== "Done" && upstream.status !== "Cancelled") continue;
25171
+ const gate = {
25172
+ taskId: task.displayId ?? task.id,
25173
+ taskTitle: task.title,
25174
+ upstreamId: upstream.displayId ?? upstream.id,
25175
+ upstreamTitle: upstream.title,
25176
+ upstreamStatus: upstream.status,
25177
+ evidence
25178
+ };
25179
+ const bucket = upstream.status === "Done" ? sweep.unblock : sweep.zombie;
25180
+ if (bucket.length < MAX_PER_BUCKET) bucket.push(gate);
25181
+ break;
25182
+ }
25183
+ }
25184
+ return sweep;
25185
+ }
25186
+ function formatDeferredGateSection(sweep) {
25187
+ if (sweep.unblock.length === 0 && sweep.zombie.length === 0) return "";
25188
+ const lines = ["## Deferred-Gate Sweep"];
25189
+ if (sweep.unblock.length > 0) {
25190
+ lines.push("");
25191
+ lines.push(`**Unblock candidates (${sweep.unblock.length})** \u2014 the upstream task is Done, so the gate has cleared. Promote with \`board_edit\` \u2192 Backlog.`);
25192
+ for (const g of sweep.unblock) {
25193
+ lines.push(`- **${g.taskId}** (${g.taskTitle}) \u2014 gated on **${g.upstreamId}** which is now Done \u2705`);
25194
+ }
25195
+ }
25196
+ if (sweep.zombie.length > 0) {
25197
+ lines.push("");
25198
+ lines.push(`**Zombie candidates (${sweep.zombie.length})** \u2014 the upstream task was CANCELLED, so this gate can never clear. Cancel or re-scope.`);
25199
+ for (const g of sweep.zombie) {
25200
+ lines.push(`- **${g.taskId}** (${g.taskTitle}) \u2014 gated on **${g.upstreamId}** which was Cancelled \u274C`);
25201
+ }
25202
+ }
25203
+ lines.push("");
25204
+ lines.push("_Advisory only \u2014 nothing was changed. Gates are read from freetext notes, so check the reference before acting._");
25205
+ return lines.join("\n");
25206
+ }
25207
+
25090
25208
  // src/tools/agent-list.ts
25091
25209
  import { readdir as readdir2, readFile as readFile7 } from "fs/promises";
25092
25210
  import { join as join14 } from "path";
@@ -25129,7 +25247,7 @@ async function listAgents(projectRoot) {
25129
25247
  var agentListTool = {
25130
25248
  name: "agent_list",
25131
25249
  description: "List the sub-agents discovered in the project's `.claude/agents/*.md` files (read-only). Returns each agent's name and description so you can see which specialised sub-agents are available before dispatching one via the Task/Agent tool. Discovery only \u2014 does not invoke or manage agents.",
25132
- annotations: { readOnlyHint: true, destructiveHint: false },
25250
+ annotations: { title: "List Agents", readOnlyHint: true, destructiveHint: false, openWorldHint: false },
25133
25251
  inputSchema: {
25134
25252
  type: "object",
25135
25253
  properties: {
@@ -25308,7 +25426,7 @@ async function runWithLimit(concurrency, tasks) {
25308
25426
  var orientTool = {
25309
25427
  name: "orient",
25310
25428
  description: "Session orientation \u2014 run this FIRST at session start before any other tool. Single call that replaces build_list + health. Returns: cycle number, task counts by status, in-progress/in-review tasks, strategy review cadence, velocity snapshot, recommended next action, and a release reminder when all cycle tasks are Done but release has not run. Read-only, does not modify any files. PAPI detects build capability from the connecting harness (clientInfo); pass `environment` only to override that detection for git-dependent recommendations (build_execute, release, review_submit).",
25311
- annotations: { readOnlyHint: true, destructiveHint: false },
25429
+ annotations: { title: "Orient Session", readOnlyHint: true, destructiveHint: false, openWorldHint: false },
25312
25430
  inputSchema: {
25313
25431
  type: "object",
25314
25432
  properties: {
@@ -25332,6 +25450,7 @@ var orientTool = {
25332
25450
  var papiTool = {
25333
25451
  ...orientTool,
25334
25452
  name: "papi",
25453
+ annotations: { title: "PAPI Status", readOnlyHint: false, destructiveHint: false, openWorldHint: false },
25335
25454
  description: 'Say "papi" to check in with Papi \u2014 an alias for `orient`. Run this FIRST at session start: it returns your cycle number, task counts, in-progress/in-review work, strategy-review cadence, a velocity snapshot, and the recommended next action. Identical to `orient` (same inputs, same output); use whichever name you prefer. Read-only.'
25336
25455
  };
25337
25456
  function countStalledP1(warnings) {
@@ -26196,6 +26315,17 @@ ${versionDrift}` : "";
26196
26315
  ${section}`;
26197
26316
  } catch {
26198
26317
  }
26318
+ let deferredGateNote = "";
26319
+ if (deepHousekeeping) {
26320
+ try {
26321
+ const sweep = await tracked("deferred-gates", () => findDeferredGates(adapter2))();
26322
+ const section = formatDeferredGateSection(sweep);
26323
+ if (section) deferredGateNote = `
26324
+
26325
+ ${section}`;
26326
+ } catch {
26327
+ }
26328
+ }
26199
26329
  tracker.mark("format-summary");
26200
26330
  const subAgents = await listAgents(config2.projectRoot);
26201
26331
  const [teamSummaryLine, releaseHistoryLine] = await Promise.all([
@@ -26204,7 +26334,7 @@ ${section}`;
26204
26334
  ]);
26205
26335
  const teamSummary = [teamSummaryLine, releaseHistoryLine].filter(Boolean).join("\n") || void 0;
26206
26336
  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`).*";
26207
- return textResponse(projectBannerNote + formatOrientSummary(healthResult, buildInfo, hierarchy, latestTag, config2.projectRoot, environment, subAgents, projectName, teamSummary, clientName) + unblockNote + feedbackResolvedNote + alertsNote + ttfvNote + reconciliationNote + mergedInProgressNote + unrecordedNote + unregisteredDocsNote + staleSkillsNote + researchSignalsNote + recsNote + pendingReviewNote + patternsNote + unactionedIssuesNote + skillProposalsNote + sessionGuidanceNote + versionNote + enrichmentNote + deliveryShapeNote + preBuildCheckNote + deepHint + enrichmentFilesSection);
26337
+ return textResponse(projectBannerNote + formatOrientSummary(healthResult, buildInfo, hierarchy, latestTag, config2.projectRoot, environment, subAgents, projectName, teamSummary, clientName) + unblockNote + deferredGateNote + feedbackResolvedNote + alertsNote + ttfvNote + reconciliationNote + mergedInProgressNote + unrecordedNote + unregisteredDocsNote + staleSkillsNote + researchSignalsNote + recsNote + pendingReviewNote + patternsNote + unactionedIssuesNote + skillProposalsNote + sessionGuidanceNote + versionNote + enrichmentNote + deliveryShapeNote + preBuildCheckNote + deepHint + enrichmentFilesSection);
26208
26338
  } catch (err) {
26209
26339
  const message = err instanceof Error ? err.message : String(err);
26210
26340
  const isKnownFriendly = /^(Orient failed|Project not found|No project|Setup required)/i.test(message);
@@ -26260,7 +26390,7 @@ function enrichClaudeMd(projectRoot, cycleNumber, adapterType, collector, client
26260
26390
  var hierarchyUpdateTool = {
26261
26391
  name: "hierarchy_update",
26262
26392
  description: "Update the status of a phase, stage, or horizon in the project hierarchy (AD-14). Accepts a level (phase, stage, or horizon), a name or ID, and a new status. For stages, optionally set exit_criteria \u2014 a checklist defining when the stage is considered done. Does not call the Anthropic API.",
26263
- annotations: { readOnlyHint: false, destructiveHint: false },
26393
+ annotations: { title: "Update Hierarchy", readOnlyHint: false, destructiveHint: false, openWorldHint: false },
26264
26394
  inputSchema: {
26265
26395
  type: "object",
26266
26396
  properties: {
@@ -26671,7 +26801,7 @@ async function applyZoomOut(adapter2, llmResponse, cycleNumber) {
26671
26801
  var zoomOutTool = {
26672
26802
  name: "zoom_out",
26673
26803
  description: 'Run a Zoom-Out Retrospective \u2014 a higher-level meta-retrospective that sits above strategy reviews. Analyses the full project arc: every cycle, decision, and pivot. Use when you want to step back and see the big picture after many cycles. First call returns a prompt (prepare phase). Then call again with mode "apply" and your output.',
26674
- annotations: { readOnlyHint: false, destructiveHint: false },
26804
+ annotations: { title: "Zoom Out", readOnlyHint: false, destructiveHint: false, openWorldHint: false },
26675
26805
  inputSchema: {
26676
26806
  type: "object",
26677
26807
  properties: {
@@ -26775,7 +26905,7 @@ ${result.userMessage}
26775
26905
  var getSiblingAdsTool = {
26776
26906
  name: "get_sibling_ads",
26777
26907
  description: "Read Active Decisions from sibling PAPI projects that share the same Supabase instance. Requires PAPI_SIBLING_PROJECT_IDS env var (comma-separated project UUIDs). Returns ADs labelled by source project \u2014 useful for cross-project architectural alignment. pg adapter only \u2014 returns an error if using md or proxy adapter.",
26778
- annotations: { readOnlyHint: true, destructiveHint: false },
26908
+ annotations: { title: "Sibling Decisions", readOnlyHint: true, destructiveHint: false, openWorldHint: false },
26779
26909
  inputSchema: {
26780
26910
  type: "object",
26781
26911
  properties: {
@@ -26852,7 +26982,7 @@ var handoffPrepareCache = new PerCallerCache();
26852
26982
  var handoffGenerateTool = {
26853
26983
  name: "handoff_generate",
26854
26984
  description: "Generate BUILD HANDOFFs for cycle tasks that don't have one yet. Run after `plan` (with skip_handoffs=true) or to regenerate stale handoffs. Uses the prepare/apply pattern \u2014 first call returns a prompt, second call persists results.",
26855
- annotations: { readOnlyHint: false, destructiveHint: false },
26985
+ annotations: { title: "Generate Handoff", readOnlyHint: false, destructiveHint: false, openWorldHint: false },
26856
26986
  inputSchema: {
26857
26987
  type: "object",
26858
26988
  properties: {
@@ -27108,7 +27238,7 @@ function buildSummary(task, taskCount) {
27108
27238
  var scopeBriefTool = {
27109
27239
  name: "scope_brief",
27110
27240
  description: "Decompose a brief-class task (Large/XL, too large to build directly) into a structured scope document. Runs an LLM pass to produce sub-tasks, writes docs/scopes/<task-id>.md, registers it in the doc registry, and marks the source task as decomposed. Use before planning a cycle that includes brief-class tasks.",
27111
- annotations: { readOnlyHint: false, destructiveHint: false },
27241
+ annotations: { title: "Scope Brief", readOnlyHint: false, destructiveHint: false, openWorldHint: false },
27112
27242
  inputSchema: {
27113
27243
  type: "object",
27114
27244
  properties: {
@@ -27157,7 +27287,7 @@ async function handleScopeBrief(adapter2, config2, args) {
27157
27287
  var adViewTool = {
27158
27288
  name: "ad_view",
27159
27289
  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.",
27160
- annotations: { readOnlyHint: true, destructiveHint: false },
27290
+ annotations: { title: "View Decisions", readOnlyHint: true, destructiveHint: false, openWorldHint: false },
27161
27291
  inputSchema: {
27162
27292
  type: "object",
27163
27293
  properties: {
@@ -27214,7 +27344,7 @@ ${formatted}`);
27214
27344
  var learningActionTool = {
27215
27345
  name: "learning_action",
27216
27346
  description: `Mark a cycle learning as actioned (linking it to a task or idea) or list unactioned learnings. Use "mark" to close out a learning after you've submitted an idea or created a task for it. Use "list" to see which learnings from recent cycles still need follow-up.`,
27217
- annotations: { readOnlyHint: false, destructiveHint: false },
27347
+ annotations: { title: "Action Learning", readOnlyHint: false, destructiveHint: false, openWorldHint: false },
27218
27348
  inputSchema: {
27219
27349
  type: "object",
27220
27350
  properties: {
@@ -27300,7 +27430,7 @@ Use \`learning_action mark\` or submit via \`idea\` to close these out.`
27300
27430
  var discoveredIssueResolveTool = {
27301
27431
  name: "discovered_issue_resolve",
27302
27432
  description: 'Mark a discovered_issue (cycle_learnings row, category="issue") as resolved. Pass the learning_id you saw in orient / learning_action list output. The row stays in the database for history, but default reads exclude it. Use this when the underlying fix has actually landed \u2014 NOT when you just created a follow-up task (that is `learning_action mark` with action_taken="task_created"). Optional `note` is recorded as resolved_by.',
27303
- annotations: { readOnlyHint: false, destructiveHint: false },
27433
+ annotations: { title: "Resolve Issue", readOnlyHint: false, destructiveHint: false, openWorldHint: false },
27304
27434
  inputSchema: {
27305
27435
  type: "object",
27306
27436
  properties: {
@@ -27399,7 +27529,7 @@ var NO_SUPPORT = "Project lifecycle tools require a hosted (proxy) or PostgreSQL
27399
27529
  var projectCreateTool = {
27400
27530
  name: "project_create",
27401
27531
  description: 'Create an EMPTY PAPI project for the current workspace (no plan, no seeded backlog) and return its id. Idempotent: re-running in the same folder (or with the same name) returns the EXISTING project instead of creating a duplicate. Use when "set up papi here" / "create a project" and none matches this folder. No Anthropic API.',
27402
- annotations: { readOnlyHint: false, destructiveHint: false },
27532
+ annotations: { title: "Create Project", readOnlyHint: false, destructiveHint: false, openWorldHint: false },
27403
27533
  inputSchema: {
27404
27534
  type: "object",
27405
27535
  properties: {
@@ -27445,7 +27575,7 @@ Run \`plan\` when you're ready to start a cycle.`
27445
27575
  var projectListTool = {
27446
27576
  name: "project_list",
27447
27577
  description: "List the PAPI projects on your account (id, name, slug, mapped folder). Use to see which project you are about to write to, or to find the id/slug to pass to project_switch or the per-call `project` arg. No Anthropic API.",
27448
- annotations: { readOnlyHint: true, destructiveHint: false },
27578
+ annotations: { title: "List Projects", readOnlyHint: true, destructiveHint: false, openWorldHint: false },
27449
27579
  inputSchema: {
27450
27580
  type: "object",
27451
27581
  properties: {
@@ -27476,7 +27606,7 @@ ${lines.join("\n")}`);
27476
27606
  var projectSwitchTool = {
27477
27607
  name: "project_switch",
27478
27608
  description: 'Select a project you own by id or slug and map the current folder to it (sets papi_dir on local stdio). Use when "switch to golf" / "point papi at <project>". Fails closed if the project is not on your account. No Anthropic API.',
27479
- annotations: { readOnlyHint: false, destructiveHint: false },
27609
+ annotations: { title: "Switch Project", readOnlyHint: false, destructiveHint: false, openWorldHint: false },
27480
27610
  inputSchema: {
27481
27611
  type: "object",
27482
27612
  properties: {
@@ -27511,7 +27641,7 @@ On hosted/remote sessions this is now your DEFAULT project \u2014 calls without
27511
27641
  var contributorAddTool = {
27512
27642
  name: "contributor_add",
27513
27643
  description: "Add a contributor to the current project by email (owner-only). The person must already have a PAPI account. Grants cohort membership on project_contributors \u2014 contributors-tier visibility, no roles yet.",
27514
- annotations: { readOnlyHint: false, destructiveHint: false },
27644
+ annotations: { title: "Add Contributor", readOnlyHint: false, destructiveHint: false, openWorldHint: false },
27515
27645
  inputSchema: {
27516
27646
  type: "object",
27517
27647
  properties: {
@@ -27523,7 +27653,7 @@ var contributorAddTool = {
27523
27653
  var contributorRemoveTool = {
27524
27654
  name: "contributor_remove",
27525
27655
  description: "Remove a contributor from the current project by email (owner-only). Deletes their project_contributors row \u2014 they lose contributors-tier visibility.",
27526
- annotations: { readOnlyHint: false, destructiveHint: true },
27656
+ annotations: { title: "Remove Contributor", readOnlyHint: false, destructiveHint: true, openWorldHint: false },
27527
27657
  inputSchema: {
27528
27658
  type: "object",
27529
27659
  properties: {
@@ -27535,7 +27665,7 @@ var contributorRemoveTool = {
27535
27665
  var contributorListTool = {
27536
27666
  name: "contributor_list",
27537
27667
  description: "List the current project's contributors (any project member). Shows each member's email, display name, role, and join date. Does not call the Anthropic API.",
27538
- annotations: { readOnlyHint: true, destructiveHint: false },
27668
+ annotations: { title: "List Contributors", readOnlyHint: true, destructiveHint: false, openWorldHint: false },
27539
27669
  inputSchema: {
27540
27670
  type: "object",
27541
27671
  properties: {
@@ -27641,7 +27771,7 @@ async function handleContributorList(adapter2, config2, _args) {
27641
27771
  var taskClaimTool = {
27642
27772
  name: "task_claim",
27643
27773
  description: "Claim a task from the shared org Pool into your personal backlog (assignee = you). Atomic first-claim-wins \u2014 a concurrent double-claim is impossible. Cascades the DEPENDS ON chain: claiming a task also claims its not-Done prerequisites; if any prerequisite is already claimed by another member the whole claim is refused (a build unit is never split across owners). Does not call the Anthropic API.",
27644
- annotations: { readOnlyHint: false, destructiveHint: false },
27774
+ annotations: { title: "Claim Task", readOnlyHint: false, destructiveHint: false, openWorldHint: false },
27645
27775
  inputSchema: {
27646
27776
  type: "object",
27647
27777
  properties: {
@@ -27653,7 +27783,7 @@ var taskClaimTool = {
27653
27783
  var taskUnclaimTool = {
27654
27784
  name: "task_unclaim",
27655
27785
  description: "Release a task you claimed back to the shared Pool (clears assignee). Claimer-only and pre-review \u2014 you cannot unclaim another member's task or one that has reached In Review/Done. Does not cascade. Does not call the Anthropic API.",
27656
- annotations: { readOnlyHint: false, destructiveHint: true },
27786
+ annotations: { title: "Unclaim Task", readOnlyHint: false, destructiveHint: true, openWorldHint: false },
27657
27787
  inputSchema: {
27658
27788
  type: "object",
27659
27789
  properties: {
@@ -27790,7 +27920,7 @@ async function handleTaskUnclaim(adapter2, config2, args) {
27790
27920
  var taskMoveTool = {
27791
27921
  name: "task_move",
27792
27922
  description: "Move a task from the current project to another project you own. Reassigns the task a fresh id in the target (collision-free) and carries its build reports, comments, and history with it; the cycle assignment is cleared so it lands in the target project's backlog. You must own (or have write access to) BOTH projects. Destructive-ish and cross-project, so it requires confirm=true \u2014 without it you get a preview only. Does not call the Anthropic API.",
27793
- annotations: { readOnlyHint: false, destructiveHint: true },
27923
+ annotations: { title: "Move Task", readOnlyHint: false, destructiveHint: true, openWorldHint: false },
27794
27924
  inputSchema: {
27795
27925
  type: "object",
27796
27926
  properties: {
@@ -27977,7 +28107,7 @@ async function syncHarnessInventory(adapter2, config2, opts) {
27977
28107
  var inventorySyncTool = {
27978
28108
  name: "inventory_sync",
27979
28109
  description: "Sync this project's harness inventory \u2014 skills, sub-agents, hooks, and MCP tools \u2014 to the database so the dashboard can surface it. Gated by a cheap change-fingerprint: a no-op when the harness hasn't changed since the last sync. Set force=true to re-scan and write regardless. Runs automatically at setup and release; use this for an explicit refresh after editing your harness.",
27980
- annotations: { readOnlyHint: false, destructiveHint: false },
28110
+ annotations: { title: "Sync Inventory", readOnlyHint: false, destructiveHint: false, openWorldHint: false },
27981
28111
  inputSchema: {
27982
28112
  type: "object",
27983
28113
  properties: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@papi-ai/server",
3
- "version": "0.7.51",
3
+ "version": "0.7.52",
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",