@papi-ai/server 0.7.74 → 0.7.75

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/index.js CHANGED
@@ -5041,7 +5041,7 @@ var setup_exports = {};
5041
5041
  __export(setup_exports, {
5042
5042
  runSetup: () => runSetup
5043
5043
  });
5044
- import { existsSync as existsSync15, readFileSync as readFileSync17, writeFileSync as writeFileSync8, chmodSync as chmodSync2, statSync as statSync8 } from "fs";
5044
+ import { existsSync as existsSync15, readFileSync as readFileSync17, writeFileSync as writeFileSync8, chmodSync as chmodSync2, statSync as statSync9 } from "fs";
5045
5045
  import { join as join24 } from "path";
5046
5046
  function baseUrl() {
5047
5047
  const fromEnv = process.env["PAPI_HOST"] ?? process.env["PAPI_BASE_URL"];
@@ -5101,7 +5101,7 @@ function writeMcpJson(opts) {
5101
5101
  parsed.mcpServers = mcpServers;
5102
5102
  writeFileSync8(path7, JSON.stringify(parsed, null, 2) + "\n", "utf-8");
5103
5103
  try {
5104
- const mode = statSync8(path7).mode & 511;
5104
+ const mode = statSync9(path7).mode & 511;
5105
5105
  if (mode !== 384) chmodSync2(path7, 384);
5106
5106
  } catch {
5107
5107
  }
@@ -5211,7 +5211,7 @@ var init_setup = __esm({
5211
5211
  // src/index.ts
5212
5212
  import { readFileSync as readFileSync18 } from "fs";
5213
5213
  import { dirname as dirname6, join as join25, basename as basename2 } from "path";
5214
- import { fileURLToPath as fileURLToPath4 } from "url";
5214
+ import { fileURLToPath as fileURLToPath5 } from "url";
5215
5215
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
5216
5216
  import { Server as Server2 } from "@modelcontextprotocol/sdk/server/index.js";
5217
5217
  import {
@@ -8257,7 +8257,7 @@ Check PAPI_PROJECT_ID in your .mcp.json config. Find your project ID in the PAPI
8257
8257
  import { readFileSync as readFileSync13 } from "fs";
8258
8258
  import { access as access4, readdir as readdir4, readFile as readFile9 } from "fs/promises";
8259
8259
  import { join as join20, dirname as dirname5 } from "path";
8260
- import { fileURLToPath as fileURLToPath3 } from "url";
8260
+ import { fileURLToPath as fileURLToPath4 } from "url";
8261
8261
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
8262
8262
  import {
8263
8263
  CallToolRequestSchema,
@@ -8284,6 +8284,8 @@ var UNIVERSAL_FRAME = `PAPI gives this project a structured plan \u2192 build \u
8284
8284
 
8285
8285
  5. VERIFY BEFORE DONE. Test the change and confirm it works before reporting a task complete. Report failures honestly, with the output.
8286
8286
 
8287
+ 6. NAME THE PROJECT WHEN YOU KNOW IT. If you know which repo this session is working in, pass \`project="<slug>"\` on the call rather than relying on whatever the connection defaults to. An account with more than one project cannot be resolved from a connection with no project bound \u2014 PAPI will stop and ask which one rather than guess, and answering costs a round trip. If PAPI asks, put the list to the user, then re-call with their choice; run \`project_switch\` to make it stick.
8288
+
8287
8289
  PAPI reads and writes all project state through these tools \u2014 they are the source of truth, not local files.`;
8288
8290
 
8289
8291
  // src/lib/response.ts
@@ -9034,7 +9036,7 @@ PRE-BUILD VERIFICATION
9034
9036
  [List 2-5 specific file paths the builder should read BEFORE implementing to check if the functionality already exists. Derive these from FILES LIKELY TOUCHED \u2014 pick the files most likely to already contain the target functionality. ALSO mandate a docs sweep, not just file-existence (task-2161): name any docs the builder should check via doc_search or the docs index \u2014 a design/research/status:final doc or a prior task may already cover the work. If >80% of the scope is already implemented, the builder should report "already built" instead of re-implementing. Include this section for EVERY task \u2014 it prevents wasted build slots on already-shipped code.]
9035
9037
 
9036
9038
  FILES LIKELY TOUCHED
9037
- [files \u2014 real paths only. TEST FILE LOCATIONS (task-2606): PAPI tests are NOT co-located next to source in a src/**/__tests__/ folder. Use the actual layout: server tests \u2192 packages/server/tests/<name>.test.ts; adapter-pg tests \u2192 packages/adapter-pg/src/__tests__/<name>.test.ts; dashboard/root tests \u2192 tests/<area>/<name>.test.ts. Do NOT invent packages/server/src/**/__tests__/ paths \u2014 they do not exist.]
9039
+ [files \u2014 real paths only. TEST FILE LOCATIONS (task-2606): PAPI tests are NOT co-located next to source in a src/**/__tests__/ folder. Use the actual layout: server tests \u2192 packages/server/tests/<name>.test.ts; adapter-pg tests \u2192 packages/adapter-pg/src/__tests__/<name>.test.ts; dashboard/root tests \u2192 tests/<area>/<name>.test.ts. Do NOT invent packages/server/src/**/__tests__/ paths \u2014 they do not exist. MCP RESTART RULE (task-2978): whenever FILES LIKELY TOUCHED includes any \`packages/server/**\` path, append this line verbatim to the handoff \u2014 "MCP RESTART REQUIRED: a mid-session \`npm run mcp-build\` writes a dist the already-booted MCP process never loads, so verifying this change through PAPI's own tools before an explicit MCP reconnect is INVALID. Implement \u2192 mcp-build \u2192 reconnect \u2192 only then verify." This is one line inside this section, not a new section.]
9038
9040
 
9039
9041
  EFFORT
9040
9042
  [XS/S/M/L/XL]
@@ -9048,7 +9050,7 @@ After your natural language output, include this EXACT format on its own line:
9048
9050
  {
9049
9051
  "cycleLogTitle": "string \u2014 short descriptive title WITHOUT 'Cycle N' prefix. Should capture the cycle theme in 3-5 words (e.g. 'MCP Quality + Product Readiness' not 'Cycle 5 \u2014 Board Triage \u2014 Bug Fix'). This is the canonical theme label for the cycle.",
9050
9052
  "cycleLogContent": "string \u2014 5-10 line cycle log body in markdown, NO heading (the ### heading is generated automatically)",
9051
- "cycleLogCarryForward": "string or null \u2014 carry-forward items for next cycle",
9053
+ "cycleLogCarryForward": "string or null \u2014 carry-forward for the next cycle, in TWO NAMED SECTIONS, product signal FIRST. Section 1 begins with the literal label 'WHAT SHIPS FOR USERS:' and names, concretely, what a person using this project can now see or do, and how to check it \u2014 never a deploy step. Section 2 begins with the literal label 'RELEASE MECHANICS:' and carries everything operational: deploy, publish, migrations, gate order, branch counts, spot-checks. The mechanics are load-bearing and must NOT be dropped or shortened \u2014 they go second, not away. orient parses these two labels and renders the product half first, so the labels are a contract, not decoration. If a cycle genuinely ships nothing user-visible, say so in one line under section 1 rather than omitting the label.",
9052
9054
  "cycleLogNotes": "string or null \u2014 1-3 lines of cycle-level observations: estimation accuracy, recurring blockers, velocity trends, dependency signals. Omit if no noteworthy observations.",
9053
9055
  "nextMode": "Full",
9054
9056
  "boardHealth": "string \u2014 e.g. 5 tasks (3 backlog, 2 done)",
@@ -9912,7 +9914,7 @@ After your natural language output, include this EXACT format on its own line:
9912
9914
  \`\`\`json
9913
9915
  {
9914
9916
  "sessionLogTitle": "string \u2014 Strategy Review title WITHOUT 'Cycle N' prefix (e.g. 'Strategy Review' not 'Cycle 5 \u2014 Strategy Review')",
9915
- "sessionLogContent": "string \u2014 5-10 line cycle log body summarizing the review, NO heading (the ### heading is generated automatically)",
9917
+ "sessionLogContent": "string \u2014 5-10 line cycle log body summarizing the review, NO heading (the ### heading is generated automatically). LEAD WITH TANGIBLE PRODUCT INSIGHT: open with what was learned about the product, its users, or the market \u2014 a specific finding a reader can act on. Board hygiene, cadence, cycle counts, branch state and other housekeeping are legitimate content but they TRAIL; they must never be the opening line. If the review's most important finding genuinely is an operational one, say why it matters to the product in the same breath rather than reporting it as admin.",
9916
9918
  "velocityAssessment": "string \u2014 2-3 sentence velocity summary",
9917
9919
  "strategicRecommendations": "string \u2014 key recommendations in markdown",
9918
9920
  "activeDecisionUpdates": [
@@ -10276,7 +10278,7 @@ REFERENCE DOCS
10276
10278
  [Optional \u2014 paths to docs/ files with background context. Omit if not needed.]
10277
10279
 
10278
10280
  FILES LIKELY TOUCHED
10279
- [files \u2014 real paths only. TEST FILE LOCATIONS (task-2606): PAPI tests are NOT co-located next to source in a src/**/__tests__/ folder. Use the actual layout: server tests \u2192 packages/server/tests/<name>.test.ts; adapter-pg tests \u2192 packages/adapter-pg/src/__tests__/<name>.test.ts; dashboard/root tests \u2192 tests/<area>/<name>.test.ts. Do NOT invent packages/server/src/**/__tests__/ paths \u2014 they do not exist.]
10281
+ [files \u2014 real paths only. TEST FILE LOCATIONS (task-2606): PAPI tests are NOT co-located next to source in a src/**/__tests__/ folder. Use the actual layout: server tests \u2192 packages/server/tests/<name>.test.ts; adapter-pg tests \u2192 packages/adapter-pg/src/__tests__/<name>.test.ts; dashboard/root tests \u2192 tests/<area>/<name>.test.ts. Do NOT invent packages/server/src/**/__tests__/ paths \u2014 they do not exist. MCP RESTART RULE (task-2978): whenever FILES LIKELY TOUCHED includes any \`packages/server/**\` path, append this line verbatim to the handoff \u2014 "MCP RESTART REQUIRED: a mid-session \`npm run mcp-build\` writes a dist the already-booted MCP process never loads, so verifying this change through PAPI's own tools before an explicit MCP reconnect is INVALID. Implement \u2192 mcp-build \u2192 reconnect \u2192 only then verify." This is one line inside this section, not a new section.]
10280
10282
 
10281
10283
  EFFORT
10282
10284
  [XS/S/M/L/XL]`;
@@ -10641,9 +10643,9 @@ async function getPrompt(name) {
10641
10643
  if (!endpoint) {
10642
10644
  return LOCAL_PROMPTS[name];
10643
10645
  }
10644
- const cached = cache.get(name);
10645
- if (cached && Date.now() - cached.fetchedAt < CACHE_TTL_MS) {
10646
- return cached.content;
10646
+ const cached2 = cache.get(name);
10647
+ if (cached2 && Date.now() - cached2.fetchedAt < CACHE_TTL_MS) {
10648
+ return cached2.content;
10647
10649
  }
10648
10650
  try {
10649
10651
  const url = endpoint.endsWith("/") ? `${endpoint}${name}` : `${endpoint}/${name}`;
@@ -10656,19 +10658,19 @@ async function getPrompt(name) {
10656
10658
  }
10657
10659
  const response = await fetch(url, { headers, signal: AbortSignal.timeout(1e4) });
10658
10660
  if (!response.ok) {
10659
- if (cached) return cached.content;
10661
+ if (cached2) return cached2.content;
10660
10662
  return LOCAL_PROMPTS[name];
10661
10663
  }
10662
10664
  const data = await response.json();
10663
10665
  const content = data.content;
10664
10666
  if (typeof content !== "string" || content.length === 0) {
10665
- if (cached) return cached.content;
10667
+ if (cached2) return cached2.content;
10666
10668
  return LOCAL_PROMPTS[name];
10667
10669
  }
10668
10670
  cache.set(name, { content, fetchedAt: Date.now() });
10669
10671
  return content;
10670
10672
  } catch {
10671
- if (cached) return cached.content;
10673
+ if (cached2) return cached2.content;
10672
10674
  return LOCAL_PROMPTS[name];
10673
10675
  }
10674
10676
  }
@@ -24283,7 +24285,7 @@ var ideaTool = {
24283
24285
  },
24284
24286
  project: {
24285
24287
  type: "string",
24286
- description: "Project id (UUID) or slug to write this idea to, overriding the session project for THIS call only. Must be a project on your account \u2014 fails closed otherwise. Use project_switch to change the session default."
24288
+ description: "Project id (UUID) or slug to write this idea to, overriding the session project for THIS call only. Must be a project on your account \u2014 fails closed otherwise. Use project_switch to change the session default. PASS THIS WHENEVER YOU KNOW WHICH REPO THE SESSION IS IN: on a multi-project account a connection with no project bound cannot be resolved, and PAPI will stop and ask rather than guess."
24287
24289
  }
24288
24290
  },
24289
24291
  required: ["text"]
@@ -24730,7 +24732,7 @@ var backlogImportTool = {
24730
24732
  },
24731
24733
  project: {
24732
24734
  type: "string",
24733
- description: "Project id (UUID) or slug to import into, overriding the session project for THIS call only. Must be a project on your account \u2014 fails closed otherwise."
24735
+ description: "Project id (UUID) or slug to import into, overriding the session project for THIS call only. Must be a project on your account \u2014 fails closed otherwise. PASS THIS WHENEVER YOU KNOW WHICH REPO THE SESSION IS IN: on a multi-project account a connection with no project bound cannot be resolved, and PAPI will stop and ask rather than guess."
24734
24736
  }
24735
24737
  },
24736
24738
  required: ["source"]
@@ -24938,7 +24940,7 @@ var bugTool = {
24938
24940
  },
24939
24941
  project: {
24940
24942
  type: "string",
24941
- description: "BOARD MODE ONLY. Project id (UUID) or slug to file this bug under, overriding the session project for THIS call only. Must be a project on your account \u2014 fails closed otherwise. It CANNOT redirect an upstream (report=true) submission \u2014 those always go to PAPI maintainers. Use project_switch to change the session default."
24943
+ description: "BOARD MODE ONLY. Project id (UUID) or slug to file this bug under, overriding the session project for THIS call only. Must be a project on your account \u2014 fails closed otherwise. It CANNOT redirect an upstream (report=true) submission \u2014 those always go to PAPI maintainers. Use project_switch to change the session default. PASS THIS WHENEVER YOU KNOW WHICH REPO THE SESSION IS IN: on a multi-project account a connection with no project bound cannot be resolved, and PAPI will stop and ask rather than guess."
24942
24944
  }
24943
24945
  },
24944
24946
  required: ["text"]
@@ -27841,6 +27843,50 @@ function formatUnblockSection(candidates) {
27841
27843
  return lines.join("\n");
27842
27844
  }
27843
27845
 
27846
+ // src/lib/carry-forward-shape.ts
27847
+ var PRODUCT_MARKER = /WHAT SHIPS FOR USERS\b[^\n:]*:/i;
27848
+ var MECHANICS_MARKER = /RELEASE MECHANICS\b[^\n:]*:/i;
27849
+ function splitCarryForward(raw) {
27850
+ const productMatch = PRODUCT_MARKER.exec(raw);
27851
+ const mechanicsMatch = MECHANICS_MARKER.exec(raw);
27852
+ if (!productMatch && !mechanicsMatch) {
27853
+ return { product: raw.trim(), mechanics: "", split: false };
27854
+ }
27855
+ if (!productMatch && mechanicsMatch) {
27856
+ const head = raw.slice(0, mechanicsMatch.index).trim();
27857
+ const tail = raw.slice(mechanicsMatch.index + mechanicsMatch[0].length).trim();
27858
+ return { product: head, mechanics: tail, split: true };
27859
+ }
27860
+ const productStart = productMatch.index + productMatch[0].length;
27861
+ if (!mechanicsMatch) {
27862
+ return { product: raw.slice(productStart).trim(), mechanics: "", split: true };
27863
+ }
27864
+ if (mechanicsMatch.index < productMatch.index) {
27865
+ return {
27866
+ product: raw.slice(productStart).trim(),
27867
+ mechanics: raw.slice(mechanicsMatch.index + mechanicsMatch[0].length, productMatch.index).trim(),
27868
+ split: true
27869
+ };
27870
+ }
27871
+ return {
27872
+ product: raw.slice(productStart, mechanicsMatch.index).trim(),
27873
+ mechanics: raw.slice(mechanicsMatch.index + mechanicsMatch[0].length).trim(),
27874
+ split: true
27875
+ };
27876
+ }
27877
+ function renderCarryForward(raw, decorate = (t) => t) {
27878
+ const { product, mechanics, split } = splitCarryForward(raw);
27879
+ if (!split) return [decorate(product)];
27880
+ const lines = [];
27881
+ if (product) lines.push(decorate(product));
27882
+ if (mechanics) {
27883
+ if (product) lines.push("");
27884
+ lines.push("**Release mechanics** \u2014 needed at release, not now:");
27885
+ lines.push(decorate(mechanics));
27886
+ }
27887
+ return lines;
27888
+ }
27889
+
27844
27890
  // src/lib/deferred-gate.ts
27845
27891
  var GATE_PHRASES = [
27846
27892
  "depends on",
@@ -28420,7 +28466,7 @@ function formatOrientSummary(health, buildInfo, hierarchy, latestTag, projectRoo
28420
28466
  const hasCarryForward = health.carryForward !== "None found" && !health.carryForward.startsWith("No carry-forward");
28421
28467
  if (hasCarryForward) {
28422
28468
  lines.push("## Carry-Forward");
28423
- lines.push(annotateTaskRefs(health.carryForward, taskRefs));
28469
+ lines.push(...renderCarryForward(health.carryForward, (t) => annotateTaskRefs(t, taskRefs)));
28424
28470
  lines.push("");
28425
28471
  }
28426
28472
  const hasMetrics = health.metricsSection !== "Could not read methodology metrics." && !health.metricsSection.includes("undefined");
@@ -30375,6 +30421,36 @@ ${d.body}`;
30375
30421
  ${formatted}`, meta));
30376
30422
  }
30377
30423
 
30424
+ // src/lib/dist-staleness.ts
30425
+ import { statSync as statSync8 } from "fs";
30426
+ import { fileURLToPath as fileURLToPath3 } from "url";
30427
+ var BOOT_MS = Date.now();
30428
+ var DEFAULT_SKEW_MS = 2e3;
30429
+ var STAT_TTL_MS = 1e4;
30430
+ function isDistStale({ bootMs, distMtimeMs, skewMs = DEFAULT_SKEW_MS }) {
30431
+ if (distMtimeMs === null || !Number.isFinite(distMtimeMs)) return false;
30432
+ return distMtimeMs > bootMs + skewMs;
30433
+ }
30434
+ var cached = null;
30435
+ function checkDistStaleness(now = Date.now()) {
30436
+ if (cached && now - cached.at < STAT_TTL_MS) return cached.stale;
30437
+ let distMtimeMs = null;
30438
+ try {
30439
+ distMtimeMs = statSync8(fileURLToPath3(import.meta.url)).mtimeMs;
30440
+ } catch {
30441
+ distMtimeMs = null;
30442
+ }
30443
+ const stale = isDistStale({ bootMs: BOOT_MS, distMtimeMs });
30444
+ cached = { at: now, stale };
30445
+ return stale;
30446
+ }
30447
+ function stalenessWarning(now = Date.now()) {
30448
+ const age = Math.round((now - BOOT_MS) / 1e3);
30449
+ return `\u26A0\uFE0F This PAPI MCP server process booted ${age}s ago and packages/server has been REBUILT since. It is still running the code it booted with, so anything you verify through these tools right now reflects the OLD bundle. Reconnect / restart the PAPI MCP server before trusting this output.
30450
+
30451
+ `;
30452
+ }
30453
+
30378
30454
  // src/tools/learning-action.ts
30379
30455
  var learningActionTool = {
30380
30456
  name: "learning_action",
@@ -31346,9 +31422,9 @@ async function checkMeter(adapter2, toolName, cacheKey) {
31346
31422
  if (!adapter2.getMeteredUsage) return { blocked: false };
31347
31423
  try {
31348
31424
  let usage;
31349
- const cached = usageCache.get(cacheKey);
31350
- if (cached && Date.now() - cached.at < CACHE_TTL_MS3) {
31351
- usage = cached.usage;
31425
+ const cached2 = usageCache.get(cacheKey);
31426
+ if (cached2 && Date.now() - cached2.at < CACHE_TTL_MS3) {
31427
+ usage = cached2.usage;
31352
31428
  } else {
31353
31429
  usage = await adapter2.getMeteredUsage();
31354
31430
  usageCache.set(cacheKey, { usage, at: Date.now() });
@@ -31537,7 +31613,7 @@ function getToolMetadata() {
31537
31613
  return PAPI_TOOLS.map((t) => ({ name: t.name, description: t.description }));
31538
31614
  }
31539
31615
  function createServer(adapter2, config2) {
31540
- const __pkgFilename = fileURLToPath3(import.meta.url);
31616
+ const __pkgFilename = fileURLToPath4(import.meta.url);
31541
31617
  const __pkgDir = dirname5(__pkgFilename);
31542
31618
  let serverVersion = "unknown";
31543
31619
  try {
@@ -31557,7 +31633,7 @@ function createServer(adapter2, config2) {
31557
31633
  "\n\u26A0 PAPI is running in md mode \u2014 your cycles are not visible on the hosted dashboard.\n Configure DATABASE_URL or sign up at https://getpapi.ai/setup to enable observability.\n\n"
31558
31634
  );
31559
31635
  }
31560
- const __filename = fileURLToPath3(import.meta.url);
31636
+ const __filename = fileURLToPath4(import.meta.url);
31561
31637
  const __dirname2 = dirname5(__filename);
31562
31638
  const skillsDir = join20(__dirname2, "..", "skills");
31563
31639
  function parseSkillFrontmatter(content) {
@@ -31859,6 +31935,15 @@ ${usageLine(decision.usage)}`;
31859
31935
  }
31860
31936
  const footer = formatMetricsFooter(elapsed, usage, contextBytes);
31861
31937
  result.content.push({ type: "text", text: footer });
31938
+ try {
31939
+ if (checkDistStaleness() && result.content.length > 0) {
31940
+ const first = result.content[0];
31941
+ if (first && typeof first.text === "string") {
31942
+ first.text = stalenessWarning() + first.text;
31943
+ }
31944
+ }
31945
+ } catch {
31946
+ }
31862
31947
  return result;
31863
31948
  });
31864
31949
  return server2;
@@ -32304,7 +32389,7 @@ async function dispatchRequest(args) {
32304
32389
  }
32305
32390
 
32306
32391
  // src/index.ts
32307
- var __dirname = dirname6(fileURLToPath4(import.meta.url));
32392
+ var __dirname = dirname6(fileURLToPath5(import.meta.url));
32308
32393
  var pkgVersion = "unknown";
32309
32394
  try {
32310
32395
  const pkg = JSON.parse(readFileSync18(join25(__dirname, "..", "package.json"), "utf-8"));
package/dist/prompts.js CHANGED
@@ -69,7 +69,7 @@ PRE-BUILD VERIFICATION
69
69
  [List 2-5 specific file paths the builder should read BEFORE implementing to check if the functionality already exists. Derive these from FILES LIKELY TOUCHED \u2014 pick the files most likely to already contain the target functionality. ALSO mandate a docs sweep, not just file-existence (task-2161): name any docs the builder should check via doc_search or the docs index \u2014 a design/research/status:final doc or a prior task may already cover the work. If >80% of the scope is already implemented, the builder should report "already built" instead of re-implementing. Include this section for EVERY task \u2014 it prevents wasted build slots on already-shipped code.]
70
70
 
71
71
  FILES LIKELY TOUCHED
72
- [files \u2014 real paths only. TEST FILE LOCATIONS (task-2606): PAPI tests are NOT co-located next to source in a src/**/__tests__/ folder. Use the actual layout: server tests \u2192 packages/server/tests/<name>.test.ts; adapter-pg tests \u2192 packages/adapter-pg/src/__tests__/<name>.test.ts; dashboard/root tests \u2192 tests/<area>/<name>.test.ts. Do NOT invent packages/server/src/**/__tests__/ paths \u2014 they do not exist.]
72
+ [files \u2014 real paths only. TEST FILE LOCATIONS (task-2606): PAPI tests are NOT co-located next to source in a src/**/__tests__/ folder. Use the actual layout: server tests \u2192 packages/server/tests/<name>.test.ts; adapter-pg tests \u2192 packages/adapter-pg/src/__tests__/<name>.test.ts; dashboard/root tests \u2192 tests/<area>/<name>.test.ts. Do NOT invent packages/server/src/**/__tests__/ paths \u2014 they do not exist. MCP RESTART RULE (task-2978): whenever FILES LIKELY TOUCHED includes any \`packages/server/**\` path, append this line verbatim to the handoff \u2014 "MCP RESTART REQUIRED: a mid-session \`npm run mcp-build\` writes a dist the already-booted MCP process never loads, so verifying this change through PAPI's own tools before an explicit MCP reconnect is INVALID. Implement \u2192 mcp-build \u2192 reconnect \u2192 only then verify." This is one line inside this section, not a new section.]
73
73
 
74
74
  EFFORT
75
75
  [XS/S/M/L/XL]
@@ -83,7 +83,7 @@ After your natural language output, include this EXACT format on its own line:
83
83
  {
84
84
  "cycleLogTitle": "string \u2014 short descriptive title WITHOUT 'Cycle N' prefix. Should capture the cycle theme in 3-5 words (e.g. 'MCP Quality + Product Readiness' not 'Cycle 5 \u2014 Board Triage \u2014 Bug Fix'). This is the canonical theme label for the cycle.",
85
85
  "cycleLogContent": "string \u2014 5-10 line cycle log body in markdown, NO heading (the ### heading is generated automatically)",
86
- "cycleLogCarryForward": "string or null \u2014 carry-forward items for next cycle",
86
+ "cycleLogCarryForward": "string or null \u2014 carry-forward for the next cycle, in TWO NAMED SECTIONS, product signal FIRST. Section 1 begins with the literal label 'WHAT SHIPS FOR USERS:' and names, concretely, what a person using this project can now see or do, and how to check it \u2014 never a deploy step. Section 2 begins with the literal label 'RELEASE MECHANICS:' and carries everything operational: deploy, publish, migrations, gate order, branch counts, spot-checks. The mechanics are load-bearing and must NOT be dropped or shortened \u2014 they go second, not away. orient parses these two labels and renders the product half first, so the labels are a contract, not decoration. If a cycle genuinely ships nothing user-visible, say so in one line under section 1 rather than omitting the label.",
87
87
  "cycleLogNotes": "string or null \u2014 1-3 lines of cycle-level observations: estimation accuracy, recurring blockers, velocity trends, dependency signals. Omit if no noteworthy observations.",
88
88
  "nextMode": "Full",
89
89
  "boardHealth": "string \u2014 e.g. 5 tasks (3 backlog, 2 done)",
@@ -947,7 +947,7 @@ After your natural language output, include this EXACT format on its own line:
947
947
  \`\`\`json
948
948
  {
949
949
  "sessionLogTitle": "string \u2014 Strategy Review title WITHOUT 'Cycle N' prefix (e.g. 'Strategy Review' not 'Cycle 5 \u2014 Strategy Review')",
950
- "sessionLogContent": "string \u2014 5-10 line cycle log body summarizing the review, NO heading (the ### heading is generated automatically)",
950
+ "sessionLogContent": "string \u2014 5-10 line cycle log body summarizing the review, NO heading (the ### heading is generated automatically). LEAD WITH TANGIBLE PRODUCT INSIGHT: open with what was learned about the product, its users, or the market \u2014 a specific finding a reader can act on. Board hygiene, cadence, cycle counts, branch state and other housekeeping are legitimate content but they TRAIL; they must never be the opening line. If the review's most important finding genuinely is an operational one, say why it matters to the product in the same breath rather than reporting it as admin.",
951
951
  "velocityAssessment": "string \u2014 2-3 sentence velocity summary",
952
952
  "strategicRecommendations": "string \u2014 key recommendations in markdown",
953
953
  "activeDecisionUpdates": [
@@ -1311,7 +1311,7 @@ REFERENCE DOCS
1311
1311
  [Optional \u2014 paths to docs/ files with background context. Omit if not needed.]
1312
1312
 
1313
1313
  FILES LIKELY TOUCHED
1314
- [files \u2014 real paths only. TEST FILE LOCATIONS (task-2606): PAPI tests are NOT co-located next to source in a src/**/__tests__/ folder. Use the actual layout: server tests \u2192 packages/server/tests/<name>.test.ts; adapter-pg tests \u2192 packages/adapter-pg/src/__tests__/<name>.test.ts; dashboard/root tests \u2192 tests/<area>/<name>.test.ts. Do NOT invent packages/server/src/**/__tests__/ paths \u2014 they do not exist.]
1314
+ [files \u2014 real paths only. TEST FILE LOCATIONS (task-2606): PAPI tests are NOT co-located next to source in a src/**/__tests__/ folder. Use the actual layout: server tests \u2192 packages/server/tests/<name>.test.ts; adapter-pg tests \u2192 packages/adapter-pg/src/__tests__/<name>.test.ts; dashboard/root tests \u2192 tests/<area>/<name>.test.ts. Do NOT invent packages/server/src/**/__tests__/ paths \u2014 they do not exist. MCP RESTART RULE (task-2978): whenever FILES LIKELY TOUCHED includes any \`packages/server/**\` path, append this line verbatim to the handoff \u2014 "MCP RESTART REQUIRED: a mid-session \`npm run mcp-build\` writes a dist the already-booted MCP process never loads, so verifying this change through PAPI's own tools before an explicit MCP reconnect is INVALID. Implement \u2192 mcp-build \u2192 reconnect \u2192 only then verify." This is one line inside this section, not a new section.]
1315
1315
 
1316
1316
  EFFORT
1317
1317
  [XS/S/M/L/XL]`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@papi-ai/server",
3
- "version": "0.7.74",
3
+ "version": "0.7.75",
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",