@papi-ai/server 0.7.105 → 0.7.108

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.
@@ -12,6 +12,10 @@ var __export = (target, all) => {
12
12
  function isSelfHostedDeployment(value) {
13
13
  return value === "1" || value?.toLowerCase() === "true";
14
14
  }
15
+ function isFossilSupabaseReference(value) {
16
+ if (!value) return false;
17
+ return value.includes(".pooler.supabase.com") || value.includes(DECOMMISSIONED_SUPABASE_PROJECT_REF);
18
+ }
15
19
  function optionalBoolean(value, fallback) {
16
20
  if (value == null || value.trim() === "") return fallback;
17
21
  return isSelfHostedDeployment(value);
@@ -94,10 +98,11 @@ function calculateCycleMetrics(reports, currentCycle, window = 5) {
94
98
  }
95
99
  return { accuracy, velocity, unparsedEffortCount };
96
100
  }
97
- var CAPABILITY_REGISTRY, CAPABILITY_KEYS, HOSTED_APP_URL, HOSTED_MCP_URL, HOSTED_SUPABASE_URL, WAB_WINDOW_DAYS, WAB_WEEK_MS, EFFORT_SCALE;
101
+ var DECOMMISSIONED_SUPABASE_PROJECT_REF, CAPABILITY_REGISTRY, CAPABILITY_KEYS, HOSTED_APP_URL, HOSTED_MCP_URL, HOSTED_SUPABASE_URL, WAB_WINDOW_DAYS, WAB_WEEK_MS, EFFORT_SCALE;
98
102
  var init_dist = __esm({
99
103
  "../shared/dist/index.js"() {
100
104
  "use strict";
105
+ DECOMMISSIONED_SUPABASE_PROJECT_REF = "guewgygcpcmrcoppihzx";
101
106
  CAPABILITY_REGISTRY = [
102
107
  // task-3384: the PLAN step. The landing page has advertised these three since
103
108
  // the v5 control beat (components/marketing/landing/ControlC.tsx) with a code
@@ -1908,11 +1913,13 @@ var init_proxy_adapter = __esm({
1908
1913
  endpoint;
1909
1914
  apiKey;
1910
1915
  projectId;
1916
+ papiDir;
1911
1917
  onAuthRejected;
1912
1918
  constructor(config) {
1913
1919
  this.endpoint = config.endpoint.replace(/\/$/, "");
1914
1920
  this.apiKey = config.apiKey;
1915
1921
  this.projectId = config.projectId ?? "";
1922
+ this.papiDir = config.papiDir ?? null;
1916
1923
  this.onAuthRejected = config.onAuthRejected;
1917
1924
  }
1918
1925
  /**
@@ -1988,6 +1995,7 @@ var init_proxy_adapter = __esm({
1988
1995
  endpoint: this.endpoint,
1989
1996
  apiKey: this.apiKey,
1990
1997
  projectId,
1998
+ papiDir: this.papiDir,
1991
1999
  onAuthRejected: this.onAuthRejected
1992
2000
  }));
1993
2001
  }
@@ -2620,7 +2628,7 @@ Check PAPI_PROJECT_ID in your .mcp.json config. Find your project ID in the PAPI
2620
2628
  }
2621
2629
  // --- Project metadata (path-identity guardrail) ---
2622
2630
  async getProjectInfo() {
2623
- const raw = await this.invoke("getProjectInfo", []);
2631
+ const raw = await this.invoke("getProjectInfo", [this.papiDir]);
2624
2632
  if (!raw) return null;
2625
2633
  return {
2626
2634
  name: raw.name,
@@ -2707,7 +2715,9 @@ Check PAPI_PROJECT_ID in your .mcp.json config. Find your project ID in the PAPI
2707
2715
  };
2708
2716
  }
2709
2717
  async listUserProjects() {
2710
- const body = await this.postRoute("project-list", {});
2718
+ const body = await this.postRoute("project-list", {
2719
+ ...this.papiDir ? { papiDir: this.papiDir } : {}
2720
+ });
2711
2721
  return body.projects ?? [];
2712
2722
  }
2713
2723
  async createUserProject(input) {
@@ -2849,6 +2859,11 @@ var PLACEHOLDER_PATTERNS = [
2849
2859
  ];
2850
2860
  function validateDatabaseUrl(connectionString) {
2851
2861
  const lower = connectionString.toLowerCase().trim();
2862
+ if (isFossilSupabaseReference(connectionString)) {
2863
+ throw new Error(
2864
+ "This config points at PAPI's old hosted database, which was migrated.\nRegenerate your config at https://getpapi.ai \u2014 your projects and data are intact."
2865
+ );
2866
+ }
2852
2867
  if (PLACEHOLDER_PATTERNS.some((p) => lower.includes(p.toLowerCase()))) {
2853
2868
  throw new Error(
2854
2869
  "DATABASE_URL contains a placeholder value and is not configured.\nReplace it with your actual Supabase connection string in .mcp.json.\nIf you don't have one yet, contact the PAPI admin for access."
@@ -2863,10 +2878,12 @@ Check your .mcp.json configuration.`
2863
2878
  }
2864
2879
  }
2865
2880
  var _connectionStatus = "offline";
2881
+ var _lastAdapterType = null;
2866
2882
  var _pathIdentityChecked = false;
2867
2883
  async function createAdapter(optionsOrType, maybePapiDir) {
2868
2884
  const options = typeof optionsOrType === "string" ? { adapterType: optionsOrType, papiDir: maybePapiDir } : optionsOrType;
2869
2885
  const { adapterType, papiDir, papiEndpoint } = options;
2886
+ _lastAdapterType = adapterType;
2870
2887
  switch (adapterType) {
2871
2888
  case "pg": {
2872
2889
  const { PgAdapter, PgPapiAdapter, configFromEnv } = await import("@papi-ai/adapter-pg");
@@ -2966,6 +2983,7 @@ Fix the PAPI_PROJECT_ID in your .mcp.json (or unset it and let PAPI resolve the
2966
2983
  );
2967
2984
  }
2968
2985
  const pgAdapter = new PgAdapter(config);
2986
+ let projectCreated = false;
2969
2987
  try {
2970
2988
  const existing = await pgAdapter.getProject(projectId);
2971
2989
  if (!existing) {
@@ -2997,12 +3015,12 @@ Fix the PAPI_PROJECT_ID in your .mcp.json (or unset it and let PAPI resolve the
2997
3015
  id: projectId,
2998
3016
  slug,
2999
3017
  name: slug,
3000
- papi_dir: papiDir,
3001
3018
  user_id: userId,
3002
3019
  root_commit_hash: rootHash ?? void 0,
3003
3020
  repo_url: originUrl ?? void 0,
3004
3021
  resolution_method: rootHash ? "root_hash" : "manual"
3005
3022
  });
3023
+ projectCreated = true;
3006
3024
  }
3007
3025
  } else {
3008
3026
  if (!existing.root_commit_hash && rootHash || !existing.repo_url && originUrl) {
@@ -3023,25 +3041,6 @@ Fix the PAPI_PROJECT_ID in your .mcp.json (or unset it and let PAPI resolve the
3023
3041
  );
3024
3042
  }
3025
3043
  }
3026
- if (!_pathIdentityChecked) {
3027
- _pathIdentityChecked = true;
3028
- const cwd = options.projectRoot ?? process.env["PAPI_PROJECT_DIR"] ?? process.cwd();
3029
- const projectName = existing.name ?? existing.slug ?? projectId;
3030
- const result = assertWorkspaceMatch({
3031
- storedPapiDir: existing.papi_dir,
3032
- projectName,
3033
- workspacePath: cwd
3034
- });
3035
- if (result && (result.action === "backfill" || result.action === "migrate")) {
3036
- try {
3037
- await pgAdapter.updateProject(projectId, { papi_dir: result.newPapiDir });
3038
- } catch (err) {
3039
- console.error(
3040
- `[papi] Failed to persist papi_dir update: ${err instanceof Error ? err.message : String(err)}`
3041
- );
3042
- }
3043
- }
3044
- }
3045
3044
  if (existing.user_id) {
3046
3045
  const configuredUserId = process.env["PAPI_USER_ID"] ?? detectUserId();
3047
3046
  if (configuredUserId && existing.user_id !== configuredUserId) {
@@ -3060,11 +3059,29 @@ Fix the PAPI_PROJECT_ID in your .mcp.json (or unset it and let PAPI resolve the
3060
3059
  } catch {
3061
3060
  }
3062
3061
  }
3063
- const adapter = new PgPapiAdapter({ ...config, userId: resolveUserId }, projectId);
3062
+ const adapter = new PgPapiAdapter({ ...config, userId: resolveUserId, papiDir }, projectId);
3064
3063
  try {
3065
3064
  await adapter.initRls();
3066
3065
  } catch {
3067
3066
  }
3067
+ if (projectCreated && papiDir && adapter.setProjectPapiDir) {
3068
+ await adapter.setProjectPapiDir(papiDir);
3069
+ }
3070
+ if (!_pathIdentityChecked && adapter.getProjectInfo && adapter.setProjectPapiDir) {
3071
+ _pathIdentityChecked = true;
3072
+ const info = await adapter.getProjectInfo();
3073
+ if (info) {
3074
+ const cwd = options.projectRoot ?? process.env["PAPI_PROJECT_DIR"] ?? process.cwd();
3075
+ const result = assertWorkspaceMatch({
3076
+ storedPapiDir: info.papi_dir,
3077
+ projectName: info.name || info.slug || projectId,
3078
+ workspacePath: cwd
3079
+ });
3080
+ if (result && (result.action === "backfill" || result.action === "migrate")) {
3081
+ await adapter.setProjectPapiDir(result.newPapiDir);
3082
+ }
3083
+ }
3084
+ }
3068
3085
  const connected = await adapter.probeConnection();
3069
3086
  if (connected) {
3070
3087
  _connectionStatus = "connected";
@@ -3103,7 +3120,8 @@ Already have an account? Make sure PAPI_DATA_API_KEY is set in your .mcp.json en
3103
3120
  const adapter = new ProxyPapiAdapter2({
3104
3121
  endpoint: dataEndpoint,
3105
3122
  apiKey: dataApiKey,
3106
- projectId: projectId || void 0
3123
+ projectId: projectId || void 0,
3124
+ papiDir
3107
3125
  });
3108
3126
  const connected = await adapter.probeConnection();
3109
3127
  if (!connected) {
package/dist/index.js CHANGED
@@ -14,6 +14,10 @@ import { randomUUID } from "crypto";
14
14
  function isSelfHostedDeployment(value) {
15
15
  return value === "1" || value?.toLowerCase() === "true";
16
16
  }
17
+ function isFossilSupabaseReference(value) {
18
+ if (!value) return false;
19
+ return value.includes(".pooler.supabase.com") || value.includes(DECOMMISSIONED_SUPABASE_PROJECT_REF);
20
+ }
17
21
  function isCapabilityEnabled(caps, key) {
18
22
  const stored = caps?.[key];
19
23
  if (stored != null) return stored !== false;
@@ -788,10 +792,11 @@ function parsePhaseBlock(block) {
788
792
  if (isNaN(order)) return null;
789
793
  return { id, slug, label, description, status, order };
790
794
  }
791
- var CAPABILITY_REGISTRY, CAPABILITY_KEYS, ASSIGNABLE_CONTRIBUTOR_ROLES, SENSITIVE_CHANGELOG_PATTERNS, CONTRIBUTOR_PRICING_URL, PROPOSAL_EVENT_TYPES, HOSTED_APP_URL, HOSTED_MCP_URL, HOSTED_SUPABASE_URL, SEVERITY_ORDER, NONE_RE, AD_HOC_PLACEHOLDER_RE, NONE_LEAD_RE, CONTRAST_LEAD_RE, MIN_REASON_LENGTH, LOW_SEVERITIES, WAB_WINDOW_DAYS, WAB_DEFAULT_WEEKS, WAB_WEEK_MS, PRODUCT_MARKER, MECHANICS_MARKER, CHECK_VALUE_MAX, VALID_EFFORT_SIZES, SECTION_HEADERS, VALID_EFFORT_SIZES2, EFFORT_SCALE, NONE_PATTERN, NONE_PATTERN2, VALID_STATUSES, PHASES_START, PHASES_END;
795
+ var DECOMMISSIONED_SUPABASE_PROJECT_REF, CAPABILITY_REGISTRY, CAPABILITY_KEYS, ASSIGNABLE_CONTRIBUTOR_ROLES, SENSITIVE_CHANGELOG_PATTERNS, CONTRIBUTOR_PRICING_URL, PROPOSAL_EVENT_TYPES, HOSTED_APP_URL, HOSTED_MCP_URL, HOSTED_SUPABASE_URL, SEVERITY_ORDER, NONE_RE, AD_HOC_PLACEHOLDER_RE, NONE_LEAD_RE, CONTRAST_LEAD_RE, MIN_REASON_LENGTH, LOW_SEVERITIES, WAB_WINDOW_DAYS, WAB_DEFAULT_WEEKS, WAB_WEEK_MS, PRODUCT_MARKER, MECHANICS_MARKER, CHECK_VALUE_MAX, VALID_EFFORT_SIZES, SECTION_HEADERS, VALID_EFFORT_SIZES2, EFFORT_SCALE, NONE_PATTERN, NONE_PATTERN2, VALID_STATUSES, PHASES_START, PHASES_END;
792
796
  var init_dist = __esm({
793
797
  "../shared/dist/index.js"() {
794
798
  "use strict";
799
+ DECOMMISSIONED_SUPABASE_PROJECT_REF = "guewgygcpcmrcoppihzx";
795
800
  CAPABILITY_REGISTRY = [
796
801
  // task-3384: the PLAN step. The landing page has advertised these three since
797
802
  // the v5 control beat (components/marketing/landing/ControlC.tsx) with a code
@@ -2744,11 +2749,13 @@ var init_proxy_adapter = __esm({
2744
2749
  endpoint;
2745
2750
  apiKey;
2746
2751
  projectId;
2752
+ papiDir;
2747
2753
  onAuthRejected;
2748
2754
  constructor(config2) {
2749
2755
  this.endpoint = config2.endpoint.replace(/\/$/, "");
2750
2756
  this.apiKey = config2.apiKey;
2751
2757
  this.projectId = config2.projectId ?? "";
2758
+ this.papiDir = config2.papiDir ?? null;
2752
2759
  this.onAuthRejected = config2.onAuthRejected;
2753
2760
  }
2754
2761
  /**
@@ -2824,6 +2831,7 @@ var init_proxy_adapter = __esm({
2824
2831
  endpoint: this.endpoint,
2825
2832
  apiKey: this.apiKey,
2826
2833
  projectId,
2834
+ papiDir: this.papiDir,
2827
2835
  onAuthRejected: this.onAuthRejected
2828
2836
  }));
2829
2837
  }
@@ -3456,7 +3464,7 @@ Check PAPI_PROJECT_ID in your .mcp.json config. Find your project ID in the PAPI
3456
3464
  }
3457
3465
  // --- Project metadata (path-identity guardrail) ---
3458
3466
  async getProjectInfo() {
3459
- const raw = await this.invoke("getProjectInfo", []);
3467
+ const raw = await this.invoke("getProjectInfo", [this.papiDir]);
3460
3468
  if (!raw) return null;
3461
3469
  return {
3462
3470
  name: raw.name,
@@ -3543,7 +3551,9 @@ Check PAPI_PROJECT_ID in your .mcp.json config. Find your project ID in the PAPI
3543
3551
  };
3544
3552
  }
3545
3553
  async listUserProjects() {
3546
- const body = await this.postRoute("project-list", {});
3554
+ const body = await this.postRoute("project-list", {
3555
+ ...this.papiDir ? { papiDir: this.papiDir } : {}
3556
+ });
3547
3557
  return body.projects ?? [];
3548
3558
  }
3549
3559
  async createUserProject(input) {
@@ -7170,6 +7180,11 @@ var PLACEHOLDER_PATTERNS = [
7170
7180
  ];
7171
7181
  function validateDatabaseUrl(connectionString) {
7172
7182
  const lower = connectionString.toLowerCase().trim();
7183
+ if (isFossilSupabaseReference(connectionString)) {
7184
+ throw new Error(
7185
+ "This config points at PAPI's old hosted database, which was migrated.\nRegenerate your config at https://getpapi.ai \u2014 your projects and data are intact."
7186
+ );
7187
+ }
7173
7188
  if (PLACEHOLDER_PATTERNS.some((p) => lower.includes(p.toLowerCase()))) {
7174
7189
  throw new Error(
7175
7190
  "DATABASE_URL contains a placeholder value and is not configured.\nReplace it with your actual Supabase connection string in .mcp.json.\nIf you don't have one yet, contact the PAPI admin for access."
@@ -7184,6 +7199,10 @@ Check your .mcp.json configuration.`
7184
7199
  }
7185
7200
  }
7186
7201
  var _connectionStatus = "offline";
7202
+ var _lastAdapterType = null;
7203
+ function getLastAdapterType() {
7204
+ return _lastAdapterType;
7205
+ }
7187
7206
  var _pathIdentityChecked = false;
7188
7207
  function getConnectionStatus() {
7189
7208
  return _connectionStatus;
@@ -7191,6 +7210,7 @@ function getConnectionStatus() {
7191
7210
  async function createAdapter(optionsOrType, maybePapiDir) {
7192
7211
  const options = typeof optionsOrType === "string" ? { adapterType: optionsOrType, papiDir: maybePapiDir } : optionsOrType;
7193
7212
  const { adapterType, papiDir, papiEndpoint } = options;
7213
+ _lastAdapterType = adapterType;
7194
7214
  switch (adapterType) {
7195
7215
  case "pg": {
7196
7216
  const { PgAdapter, PgPapiAdapter, configFromEnv } = await import("@papi-ai/adapter-pg");
@@ -7290,6 +7310,7 @@ Fix the PAPI_PROJECT_ID in your .mcp.json (or unset it and let PAPI resolve the
7290
7310
  );
7291
7311
  }
7292
7312
  const pgAdapter = new PgAdapter(config2);
7313
+ let projectCreated = false;
7293
7314
  try {
7294
7315
  const existing = await pgAdapter.getProject(projectId);
7295
7316
  if (!existing) {
@@ -7321,12 +7342,12 @@ Fix the PAPI_PROJECT_ID in your .mcp.json (or unset it and let PAPI resolve the
7321
7342
  id: projectId,
7322
7343
  slug,
7323
7344
  name: slug,
7324
- papi_dir: papiDir,
7325
7345
  user_id: userId,
7326
7346
  root_commit_hash: rootHash ?? void 0,
7327
7347
  repo_url: originUrl ?? void 0,
7328
7348
  resolution_method: rootHash ? "root_hash" : "manual"
7329
7349
  });
7350
+ projectCreated = true;
7330
7351
  }
7331
7352
  } else {
7332
7353
  if (!existing.root_commit_hash && rootHash || !existing.repo_url && originUrl) {
@@ -7347,25 +7368,6 @@ Fix the PAPI_PROJECT_ID in your .mcp.json (or unset it and let PAPI resolve the
7347
7368
  );
7348
7369
  }
7349
7370
  }
7350
- if (!_pathIdentityChecked) {
7351
- _pathIdentityChecked = true;
7352
- const cwd = options.projectRoot ?? process.env["PAPI_PROJECT_DIR"] ?? process.cwd();
7353
- const projectName = existing.name ?? existing.slug ?? projectId;
7354
- const result = assertWorkspaceMatch({
7355
- storedPapiDir: existing.papi_dir,
7356
- projectName,
7357
- workspacePath: cwd
7358
- });
7359
- if (result && (result.action === "backfill" || result.action === "migrate")) {
7360
- try {
7361
- await pgAdapter.updateProject(projectId, { papi_dir: result.newPapiDir });
7362
- } catch (err) {
7363
- console.error(
7364
- `[papi] Failed to persist papi_dir update: ${err instanceof Error ? err.message : String(err)}`
7365
- );
7366
- }
7367
- }
7368
- }
7369
7371
  if (existing.user_id) {
7370
7372
  const configuredUserId = process.env["PAPI_USER_ID"] ?? detectUserId();
7371
7373
  if (configuredUserId && existing.user_id !== configuredUserId) {
@@ -7384,11 +7386,29 @@ Fix the PAPI_PROJECT_ID in your .mcp.json (or unset it and let PAPI resolve the
7384
7386
  } catch {
7385
7387
  }
7386
7388
  }
7387
- const adapter2 = new PgPapiAdapter({ ...config2, userId: resolveUserId }, projectId);
7389
+ const adapter2 = new PgPapiAdapter({ ...config2, userId: resolveUserId, papiDir }, projectId);
7388
7390
  try {
7389
7391
  await adapter2.initRls();
7390
7392
  } catch {
7391
7393
  }
7394
+ if (projectCreated && papiDir && adapter2.setProjectPapiDir) {
7395
+ await adapter2.setProjectPapiDir(papiDir);
7396
+ }
7397
+ if (!_pathIdentityChecked && adapter2.getProjectInfo && adapter2.setProjectPapiDir) {
7398
+ _pathIdentityChecked = true;
7399
+ const info = await adapter2.getProjectInfo();
7400
+ if (info) {
7401
+ const cwd = options.projectRoot ?? process.env["PAPI_PROJECT_DIR"] ?? process.cwd();
7402
+ const result = assertWorkspaceMatch({
7403
+ storedPapiDir: info.papi_dir,
7404
+ projectName: info.name || info.slug || projectId,
7405
+ workspacePath: cwd
7406
+ });
7407
+ if (result && (result.action === "backfill" || result.action === "migrate")) {
7408
+ await adapter2.setProjectPapiDir(result.newPapiDir);
7409
+ }
7410
+ }
7411
+ }
7392
7412
  const connected = await adapter2.probeConnection();
7393
7413
  if (connected) {
7394
7414
  _connectionStatus = "connected";
@@ -7427,7 +7447,8 @@ Already have an account? Make sure PAPI_DATA_API_KEY is set in your .mcp.json en
7427
7447
  const adapter2 = new ProxyPapiAdapter2({
7428
7448
  endpoint: dataEndpoint,
7429
7449
  apiKey: dataApiKey,
7430
- projectId: projectId || void 0
7450
+ projectId: projectId || void 0,
7451
+ papiDir
7431
7452
  });
7432
7453
  const connected = await adapter2.probeConnection();
7433
7454
  if (!connected) {
@@ -7556,21 +7577,27 @@ import {
7556
7577
  // src/universal-frame.ts
7557
7578
  var UNIVERSAL_FRAME = `PAPI gives this project a structured plan \u2192 build \u2192 review cycle, persisted across sessions. Follow it:
7558
7579
 
7559
- 0. NEW HERE? If PAPI has never been set up for this project (no cycles yet, or \`orient\` says the board is empty), call \`setup\` FIRST \u2014 it generates the Product Brief and scaffolds the workflow. Then run \`plan\` to create the first cycle. This is the required first step for a brand-new project; everything below assumes setup has already run.
7580
+ 0a. MCHAT REALITY. Most hosts load MCP tools at CONVERSATION START \u2014 tools added or authenticated mid-chat are invisible in that chat. If the user says PAPI "is not working" or "failed to connect" but your tool list has no papi tools, do NOT retry the install or re-run OAuth: the link almost certainly succeeded already. Tell them plainly: "PAPI connected fine \u2014 tools only load in a NEW conversation. Start a fresh chat and run \`papi\` there." Never claim the connection failed when you simply cannot see the tools.
7581
+
7582
+ 0b. ONE CHAT, ONE PROJECT. Each conversation binds to ONE PAPI project at a time. If the user has several projects, orient names which one is connected \u2014 confirm it matches the repo the user is working in before writing anything. Wrong project? Run \`project_switch\` (or pass \`project="<slug>"\` per call), and say a fresh conversation is the cleanest switch.
7583
+
7584
+ 1. NEW HERE? If PAPI has never been set up for this project (no cycles yet, or \`orient\` says the board is empty), call \`setup\` FIRST \u2014 it generates the Product Brief and scaffolds the workflow. Then run \`plan\` to create the first cycle. This is the required first step for a brand-new project; everything below assumes setup has already run.
7560
7585
 
7561
- 1. ORIENT FIRST (once set up). At the start of every session, call \`orient\` (or \`papi\`) before anything else \u2014 it returns the current cycle, what's in flight, and the recommended next action. Re-run it after any context compression.
7586
+ 2. ORIENT FIRST (once set up). At the start of every session, call \`orient\` (or \`papi\`) before anything else \u2014 it returns the current cycle, what's in flight, and the recommended next action. Re-run it after any context compression.
7562
7587
 
7563
- 2. THE CYCLE, IN ORDER: \`plan\` (once per cycle) \u2192 \`build_list\` (pick a task) \u2192 \`build_execute <task>\` to start \u2192 implement the task from its BUILD HANDOFF \u2192 \`build_execute\` again to complete with a build report \u2192 \`review_submit\` \u2192 \`release\` when every cycle task is done.
7588
+ 3. THE CYCLE, IN ORDER: \`plan\` (once per cycle) \u2192 \`build_list\` (pick a task) \u2192 \`build_execute <task>\` to start \u2192 implement the task from its BUILD HANDOFF \u2192 \`build_execute\` again to complete with a build report \u2192 \`review_submit\` \u2192 \`release\` when every cycle task is done.
7564
7589
 
7565
- 3. STATUS DISCIPLINE. Check a task's status before acting. "In Review" = already built \u2014 never re-build it; submit a review instead. "In Progress" = a build started \u2014 check the existing branch before writing new code. "Backlog" = not started, but check for an existing feature branch first.
7590
+ 4. STATUS DISCIPLINE. Check a task's status before acting. "In Review" = already built \u2014 never re-build it; submit a review instead. "In Progress" = a build started \u2014 check the existing branch before writing new code. "Backlog" = not started, but check for an existing feature branch first.
7566
7591
 
7567
- 4. STAY IN SCOPE. Build exactly what the BUILD HANDOFF specifies. Mechanical steps (branch creation, commits, status updates) are automatic \u2014 only "what to build" needs the user's confirmation.
7592
+ 5. STAY IN SCOPE. Build exactly what the BUILD HANDOFF specifies. Mechanical steps (branch creation, commits, status updates) are automatic \u2014 only "what to build" needs the user's confirmation.
7568
7593
 
7569
- 5. VERIFY BEFORE DONE. Test the change and confirm it works before reporting a task complete. Report failures honestly, with the output.
7594
+ 6. VERIFY BEFORE DONE. Test the change and confirm it works before reporting a task complete. Report failures honestly, with the output.
7570
7595
 
7571
- 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.
7596
+ 7. 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.
7572
7597
 
7573
- PAPI reads and writes all project state through these tools \u2014 they are the source of truth, not local files.`;
7598
+ PAPI reads and writes all project state through these tools \u2014 they are the source of truth, not local files.
7599
+
7600
+ DATA HANDLING: PAPI stores structured project metadata only (never your source code), runs no AI calls from its servers \u2014 https://getpapi.ai/trust states exactly who can read what.`;
7574
7601
 
7575
7602
  // src/lib/response.ts
7576
7603
  function textResponse(text, usage) {
@@ -12398,6 +12425,19 @@ ${cleanContent}`;
12398
12425
  `task-3670 self-heal: ${repairedCycles.length} task(s) had lost cycle membership and were re-assigned (${repairedCycles.join(", ")})`
12399
12426
  );
12400
12427
  }
12428
+ let health;
12429
+ try {
12430
+ health = await adapter2.getCycleHealth?.();
12431
+ } catch (err) {
12432
+ verifyWarnings.push(
12433
+ `task-3733: could not re-verify the cycle row after write (${err instanceof Error ? err.message : String(err)}) \u2014 cycle membership was written and repaired, but this final check could not confirm it.`
12434
+ );
12435
+ }
12436
+ if (health && health.totalCycles !== newCycleNumber) {
12437
+ throw new Error(
12438
+ `Plan apply committed, but the cycle row for Cycle ${newCycleNumber} did not take: getCycleHealth() reports the caller's current cycle as ${health.totalCycles}. Task assignments were written and repaired, but the cycle entity itself is stale \u2014 investigate the planWriteBack cycle upsert before trusting this apply.`
12439
+ );
12440
+ }
12401
12441
  const allWarnings = [...result.warnings, ...verifyWarnings, ...earlyCreateWarnings];
12402
12442
  const handoffCount = data.cycleHandoffs?.length ?? 0;
12403
12443
  const correctionCount = data.boardCorrections?.length ?? 0;
@@ -12805,6 +12845,13 @@ async function resolveCallerLatestCycle(adapter2, callerUserId) {
12805
12845
  }
12806
12846
  return newest;
12807
12847
  }
12848
+ function unownedCycleNumbers(cycles) {
12849
+ const unowned = /* @__PURE__ */ new Set();
12850
+ for (const c of cycles) {
12851
+ if (c.userId == null) unowned.add(c.number);
12852
+ }
12853
+ return unowned;
12854
+ }
12808
12855
  async function validateAndPrepare(adapter2, force, callerUserId, adapterType) {
12809
12856
  let mode;
12810
12857
  let cycleNumber;
@@ -12829,23 +12876,37 @@ Run \`release\` first, or pass \`force: true\` to bypass this block.`
12829
12876
  );
12830
12877
  }
12831
12878
  if (!force) {
12879
+ const callerCycleNumbers = callerUserId ? (await adapter2.readCycles()).filter((c) => c.userId === callerUserId).map((c) => c.number) : null;
12832
12880
  const inReviewTasks = await adapter2.queryBoard({
12833
12881
  status: ["In Review"],
12834
12882
  compact: true
12835
12883
  });
12836
- const staleTasks = inReviewTasks.filter(
12837
- (t) => t.cycle !== void 0 && t.cycle <= cycleNumber - 2
12884
+ const staleMine = inReviewTasks.filter(
12885
+ (t) => t.cycle !== void 0 && t.cycle <= cycleNumber - 2 && (callerCycleNumbers === null || callerCycleNumbers.includes(t.cycle))
12838
12886
  );
12839
- if (staleTasks.length > 0) {
12840
- const taskList = staleTasks.map((t) => `- ${t.id} (Cycle ${t.cycle}): ${t.title}`).join("\n");
12887
+ if (staleMine.length > 0) {
12888
+ const taskList = staleMine.map((t) => `- ${t.id} (Cycle ${t.cycle}): ${t.title}`).join("\n");
12841
12889
  throw new Error(
12842
- `Stale reviews detected \u2014 ${staleTasks.length} task(s) have been In Review for 2+ cycles:
12890
+ `Stale reviews detected \u2014 ${staleMine.length} task(s) have been In Review for 2+ cycles:
12843
12891
 
12844
12892
  ${taskList}
12845
12893
 
12846
12894
  Run \`review_submit\` to clear them, or pass \`force: true\` to bypass this block.`
12847
12895
  );
12848
12896
  }
12897
+ if (callerCycleNumbers !== null) {
12898
+ const unowned = unownedCycleNumbers(await adapter2.readCycles());
12899
+ const staleTheirs = inReviewTasks.filter(
12900
+ (t) => t.cycle !== void 0 && t.cycle <= cycleNumber - 2 && !callerCycleNumbers.includes(t.cycle) && // A task in a cycle with no known owner is unowned legacy data —
12901
+ // do not attribute it to a teammate either.
12902
+ !unowned.has(t.cycle)
12903
+ );
12904
+ if (staleTheirs.length > 0) {
12905
+ strategyReviewWarning += `> \u2139\uFE0F ${staleTheirs.length} task(s) are In Review from other members' cycles (2+ cycles stale). They do not block your planning \u2014 but the review queue may need attention.
12906
+
12907
+ `;
12908
+ }
12909
+ }
12849
12910
  }
12850
12911
  const autoCompleted = await assertSingleActiveCycle(adapter2, { autoComplete: force === true, userId: callerUserId ?? void 0 });
12851
12912
  for (const note of autoCompleted) {
@@ -22064,60 +22125,87 @@ Releasing to a non-base branch (e.g. dev) is unaffected.` + resolutionNote
22064
22125
  }
22065
22126
  const releaseRole = callerRole === "release_manager" ? "release manager" : "editor";
22066
22127
  tracker.mark("contributor-pr-reconciliation");
22128
+ const closeContributorCycle = (cycleOverride) => closeCycleState(
22129
+ config2,
22130
+ adapter2,
22131
+ version,
22132
+ cycleOverride ?? cycleToClose ?? void 0,
22133
+ {
22134
+ force: force ?? false,
22135
+ skipVersion: skipVersion ?? false,
22136
+ callerUserId: gate.callerUserId
22137
+ }
22138
+ );
22139
+ let reconciled = null;
22067
22140
  try {
22068
- const reconciled = await reconcileContributorReleasePrs(config2, adapter2, gate.callerUserId, cycleToClose);
22069
- if (reconciled.merged.length > 0) {
22070
- const latest = reconciled.merged[0];
22071
- let caps2 = {};
22072
- try {
22073
- const info = adapter2.getProjectInfo ? await adapter2.getProjectInfo() : null;
22074
- caps2 = info?.capabilities ?? {};
22075
- } catch {
22076
- caps2 = {};
22077
- }
22078
- await recordReadinessVerified(tracker);
22079
- await recordQualityGate(tracker, evaluateReleaseGate(caps2, config2.gateCommand, gateResult), caps2);
22080
- await completeRelease(tracker, {
22081
- cycleClosed: latest.cycle,
22082
- version,
22083
- caps: caps2,
22084
- branchMerges: latest.branch ? [{ branch: latest.branch, prUrl: latest.prUrl }] : [],
22085
- changelogEmitted: false
22086
- });
22087
- const localHandoff = isHostedTransport() ? `
22141
+ reconciled = await reconcileContributorReleasePrs(config2, adapter2, gate.callerUserId, cycleToClose);
22142
+ } catch (err) {
22143
+ console.error(`[release] contributor PR reconciliation failed (non-blocking): ${err instanceof Error ? err.message : String(err)}`);
22144
+ }
22145
+ if (reconciled?.merged.length) {
22146
+ const latest = reconciled.merged[0];
22147
+ let caps2 = {};
22148
+ try {
22149
+ const info = adapter2.getProjectInfo ? await adapter2.getProjectInfo() : null;
22150
+ caps2 = info?.capabilities ?? {};
22151
+ } catch {
22152
+ caps2 = {};
22153
+ }
22154
+ let closed;
22155
+ try {
22156
+ closed = await closeContributorCycle(latest.cycle);
22157
+ } catch (err) {
22158
+ return errorResponse(err instanceof Error ? err.message : String(err));
22159
+ }
22160
+ await recordReadinessVerified(tracker);
22161
+ await recordQualityGate(tracker, evaluateReleaseGate(caps2, config2.gateCommand, gateResult), caps2);
22162
+ await completeRelease(tracker, {
22163
+ cycleClosed: closed.resolvedCycleNum > 0 ? closed.resolvedCycleNum : latest.cycle,
22164
+ version,
22165
+ caps: caps2,
22166
+ branchMerges: latest.branch ? [{ branch: latest.branch, prUrl: latest.prUrl }] : [],
22167
+ changelogEmitted: false
22168
+ });
22169
+ const localHandoff = isHostedTransport() ? `
22088
22170
  ${buildHostedGitDirective({
22089
- version,
22090
- branch,
22091
- skipVersion: skipVersion ?? false,
22092
- harnessCanBuild: detectHarness(clientName).build,
22093
- harnessKnown: detectHarness(clientName).known,
22094
- harnessLabel: detectHarness(clientName).label,
22095
- changelogSection: null
22096
- })}` : "";
22097
- return textResponse(
22098
- `## Release ${version} \u2014 contributor PR merged
22171
+ version,
22172
+ branch,
22173
+ skipVersion: skipVersion ?? false,
22174
+ harnessCanBuild: detectHarness(clientName).build,
22175
+ harnessKnown: detectHarness(clientName).known,
22176
+ harnessLabel: detectHarness(clientName).label,
22177
+ changelogSection: null
22178
+ })}` : "";
22179
+ const cycleLabel = closed.resolvedCycleNum > 0 ? `Cycle ${closed.resolvedCycleNum}` : latest.cycle ? `Cycle ${latest.cycle}` : "The contributor cycle";
22180
+ return textResponse(
22181
+ `## Release ${version} \u2014 contributor PR merged
22099
22182
 
22100
22183
  PAPI detected that ${latest.prUrl} was merged on GitHub and reconciled the contributor release. GitHub granted the merge permission; your PAPI **${releaseRole}** role granted the release workflow.
22101
22184
 
22102
- ${latest.cycle ? `Cycle ${latest.cycle}` : "The contributor cycle"} remains **complete**, and the release is now recorded as **released** in PAPI.` + localHandoff + `
22185
+ ${cycleLabel} remains **complete**, and the release is now recorded as **released** in PAPI.` + localHandoff + `
22103
22186
 
22104
22187
  Next: run \`plan\` to start your next cycle.`
22105
- );
22188
+ );
22189
+ }
22190
+ if (reconciled?.open.length) {
22191
+ const openCycle = cycleToClose !== null ? reconciled.open.find((pr) => pr.cycle === cycleToClose)?.cycle ?? null : reconciled.open[0].cycle;
22192
+ if (openCycle !== null && openCycle !== void 0) {
22193
+ try {
22194
+ await closeContributorCycle(openCycle);
22195
+ } catch (err) {
22196
+ return errorResponse(err instanceof Error ? err.message : String(err));
22197
+ }
22106
22198
  }
22107
- if (reconciled.open.length > 0) {
22108
- const prLines = reconciled.open.map((p) => `- ${p.prUrl}`).join("\n");
22109
- return textResponse(
22110
- `## Release ${version} \u2014 contributor PR awaiting merge
22199
+ const prLines = reconciled.open.map((p) => `- ${p.prUrl}`).join("\n");
22200
+ return textResponse(
22201
+ `## Release ${version} \u2014 contributor PR awaiting merge
22111
22202
 
22112
22203
  ${prLines}
22113
22204
 
22114
22205
  Your PAPI **${releaseRole}** role allowed this release PR. Merge permission is controlled separately by GitHub: if GitHub lets you merge, review and merge it there; otherwise a repository maintainer must merge it.
22115
22206
 
22116
22207
  After it is merged, run \`release\` again so PAPI can detect the merge and finish the release record.`
22117
- );
22118
- }
22119
- } catch (err) {
22120
- console.error(`[release] contributor PR reconciliation failed (non-blocking): ${err instanceof Error ? err.message : String(err)}`);
22208
+ );
22121
22209
  }
22122
22210
  if (isHostedTransport()) {
22123
22211
  return errorResponse(
@@ -23056,7 +23144,7 @@ function getUnresolvedDeps(task, allTasks) {
23056
23144
  const taskStatusMap = new Map(allTasks.map((t) => [t.id, t.status]));
23057
23145
  return deps.filter((depId) => {
23058
23146
  const status = taskStatusMap.get(depId);
23059
- return status !== "Done";
23147
+ return status !== "Done" && status !== "Cancelled" && status !== "Archived";
23060
23148
  });
23061
23149
  }
23062
23150
  function resolveDepCycleBranch(cycleBranches, cycleNumber, upstreamModule, dependentModule) {
@@ -29879,6 +29967,30 @@ async function handleReviewClaim(adapter2, config2, args) {
29879
29967
  import { randomUUID as randomUUID12 } from "crypto";
29880
29968
  import { access as access2, readFile as readFile5, writeFile as writeFile3 } from "fs/promises";
29881
29969
  import path6 from "path";
29970
+ init_dist();
29971
+ var FOSSIL_CONFIG_MESSAGE = [
29972
+ "This config points at PAPI's old hosted database, which was migrated.",
29973
+ "Regenerate your config at https://getpapi.ai \u2014 your projects and data are intact."
29974
+ ].join("\n");
29975
+ function extractPapiOwnedConfigText(existingConfig, agent) {
29976
+ if (agent === "codex") {
29977
+ const match = existingConfig.match(/\[mcp_servers\.papi\][\s\S]*?(?=\n\[|$)/);
29978
+ return match ? match[0] : existingConfig;
29979
+ }
29980
+ if (agent === "hermes") {
29981
+ const match = existingConfig.match(/^[ \t]*papi[ \t]*:[\s\S]*?(?=\n[ \t]{0,2}\S[^\n]*:[ \t]*$|$)/m);
29982
+ return match ? match[0] : existingConfig;
29983
+ }
29984
+ try {
29985
+ const parsed = JSON.parse(existingConfig);
29986
+ const container = parsed.mcpServers ?? parsed.servers ?? parsed.mcp;
29987
+ const papiEntry = container?.papi;
29988
+ const env = papiEntry?.env ?? papiEntry?.environment;
29989
+ if (env) return JSON.stringify(env);
29990
+ } catch {
29991
+ }
29992
+ return existingConfig;
29993
+ }
29882
29994
  var initTool = {
29883
29995
  name: "init",
29884
29996
  description: "Write the MCP config file that connects this project to PAPI. Generates .mcp.json, or the equivalent for whichever MCP client you use \u2014 Claude Code, 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.",
@@ -30155,6 +30267,11 @@ async function handleInit(config2, args) {
30155
30267
  await access2(mcpJsonPath);
30156
30268
  existingConfig = await readFile5(mcpJsonPath, "utf-8");
30157
30269
  } catch {
30270
+ }
30271
+ if (existingConfig && isFossilSupabaseReference(extractPapiOwnedConfigText(existingConfig, agent))) {
30272
+ return errorResponse(`${FOSSIL_CONFIG_MESSAGE}
30273
+
30274
+ Path: ${mcpJsonPath}`);
30158
30275
  }
30159
30276
  if (existingConfig && !force) {
30160
30277
  const hasPapiServer = (() => {
@@ -30308,6 +30425,9 @@ ${writeNote}
30308
30425
  ` + formatFilesToWriteSection(collector)
30309
30426
  );
30310
30427
  }
30428
+ if (isDatabaseUser && isFossilSupabaseReference(process.env.DATABASE_URL)) {
30429
+ return errorResponse(FOSSIL_CONFIG_MESSAGE);
30430
+ }
30311
30431
  if (isDatabaseUser) {
30312
30432
  const projectId = randomUUID12();
30313
30433
  const envVars = {
@@ -30346,7 +30466,7 @@ ${writeNote}
30346
30466
  "",
30347
30467
  "## Next Steps",
30348
30468
  "",
30349
- ...process.env.DATABASE_URL ? ["1. **Restart your MCP client** to pick up the new config."] : [`1. **Set your DATABASE_URL** \u2014 replace \`<YOUR_DATABASE_URL>\` in \`${target.displayPath}\` with your Supabase **session pooler** connection string (port **5432**, not 6543 \u2014 the transaction pooler wedges on interrupted calls).`],
30469
+ ...process.env.DATABASE_URL ? ["1. **Restart your MCP client** to pick up the new config."] : [`1. **Set your DATABASE_URL** \u2014 replace \`<YOUR_DATABASE_URL>\` in \`${target.displayPath}\` with the connection string for the self-hosted Postgres/Auth stack you are running (production resolves this over the internal-only VPS tunnel \u2014 see \`infra/vps/self-host-runbook.md\`). This is not a managed Supabase pooler string.`],
30350
30470
  ...process.env.PAPI_USER_ID ? [] : ["2. **Set your PAPI_USER_ID** \u2014 replace `<YOUR_ACCOUNT_UUID>` with your account UUID (getpapi.ai \u2192 Settings \u2192 Account). Release and reviews use it to recognise you as the owner."],
30351
30471
  `${process.env.PAPI_USER_ID ? "2" : "3"}. **Run \`setup\`** \u2014 this scaffolds your project with a Product Brief, Active Decisions, and CLAUDE.md.`
30352
30472
  ].join("\n");
@@ -30394,6 +30514,9 @@ function formatWorkspaceModeLine(result) {
30394
30514
  }
30395
30515
 
30396
30516
  // src/services/health.ts
30517
+ function formatDegradedConnectionLabel() {
30518
+ return getLastAdapterType() === "proxy" ? "connection degraded \u2014 data may be stale. Regenerate your config at https://getpapi.ai if this persists." : "connection degraded \u2014 data may be stale. Check DATABASE_URL in .mcp.json";
30519
+ }
30397
30520
  function computeZoomOutWarning(cycleNumber, lastZoomOutCycle) {
30398
30521
  if (cycleNumber <= 0) return "";
30399
30522
  const baseline = lastZoomOutCycle > 0 ? lastZoomOutCycle : 1;
@@ -31501,7 +31624,7 @@ function formatOrientSummary(health, buildInfo, hierarchy, latestTag, projectRoo
31501
31624
  lines.push("");
31502
31625
  if (health.connectionStatus !== "offline") {
31503
31626
  const statusIcon = health.connectionStatus === "connected" ? "\u2713" : "\u26A0\uFE0F";
31504
- const statusLabel = health.connectionStatus === "connected" ? "Supabase connected" : "Supabase degraded \u2014 data may be stale. Check DATABASE_URL in .mcp.json";
31627
+ const statusLabel = health.connectionStatus === "connected" ? "connected" : formatDegradedConnectionLabel();
31505
31628
  lines.push(`**Connection:** ${statusIcon} ${statusLabel}`);
31506
31629
  lines.push("");
31507
31630
  }
@@ -32105,10 +32228,18 @@ async function handleOrient(rawAdapter, config2, args = {}, clientName, serverVe
32105
32228
  const handshake = buildHandshakeResult(proxyVersion);
32106
32229
  return handshake.mismatch && handshake.warning ? handshake.warning : void 0;
32107
32230
  }),
32108
- // P1 stall warning: P1 High tasks in Backlog that haven't moved in 3+ cycles
32231
+ // P1 stall warning: P1 High tasks in Backlog that haven't moved in 3+ cycles.
32232
+ // task-3736: the read is scoped to the CALLER (unassigned-or-mine, the same
32233
+ // predicate task-3397 pinned). Unscoped, a teammate's Backlog P1s were aged
32234
+ // against the CALLER's cycle count — a member at cycle 372 reading a
32235
+ // teammate's cycle-5 task as "367+ cycles" stalled. Cycle identity is
32236
+ // (project_id, user_id, number) since task-3335; cross-member cycle-age
32237
+ // arithmetic is meaningless. The caller's own unclaimed pool P1s still
32238
+ // count — unclaimed work is everyone's to claim, and its createdCycle is
32239
+ // the cycle that planned it.
32109
32240
  tracked("p1-stall", async () => {
32110
32241
  const p1BacklogTasks = await adapter2.queryBoard({ status: ["Backlog"], priority: ["P1 High"] });
32111
- const stalledP1 = p1BacklogTasks.filter(
32242
+ const stalledP1 = scopeTasksToCaller(p1BacklogTasks, callerUserId).filter(
32112
32243
  (t) => t.createdCycle != null && currentCycle2 - t.createdCycle >= 3
32113
32244
  );
32114
32245
  if (stalledP1.length === 0) return void 0;
@@ -36285,8 +36416,11 @@ function startHttpTransport(opts) {
36285
36416
  const installClient = typeof clientHeader === "string" && KNOWN_INSTALL_CLIENTS.has(clientHeader) ? clientHeader : "direct";
36286
36417
  const projectIdHeader = req.headers["x-papi-project-id"];
36287
36418
  const projectId = typeof projectIdHeader === "string" && projectIdHeader.length > 0 ? projectIdHeader : void 0;
36288
- if (req.method !== "POST" && req.method !== "GET") {
36289
- sendError(res, { status: 405, body: { error: "Method not allowed" } });
36419
+ if (req.method !== "POST") {
36420
+ sendError(res, {
36421
+ status: 405,
36422
+ body: { error: "method_not_allowed", reason: "server does not support server-initiated SSE; use POST" }
36423
+ });
36290
36424
  return;
36291
36425
  }
36292
36426
  const chunks = [];
@@ -36567,6 +36701,12 @@ async function dispatchRequest(args) {
36567
36701
  }
36568
36702
  }
36569
36703
 
36704
+ // src/lib/version-check.ts
36705
+ init_dist();
36706
+ function shouldCheckForUpdate(version, selfHostFlag) {
36707
+ return version !== "unknown" && !isSelfHostedDeployment(selfHostFlag);
36708
+ }
36709
+
36570
36710
  // src/index.ts
36571
36711
  init_dist();
36572
36712
  var __dirname = dirname7(fileURLToPath5(import.meta.url));
@@ -36711,7 +36851,7 @@ If you already have an account, check that both **PAPI_PROJECT_ID** and **PAPI_D
36711
36851
  }));
36712
36852
  }
36713
36853
  }
36714
- if (pkgVersion !== "unknown") {
36854
+ if (shouldCheckForUpdate(pkgVersion, process.env["PAPI_SELF_HOST"])) {
36715
36855
  (async () => {
36716
36856
  try {
36717
36857
  const controller = new AbortController();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@papi-ai/server",
3
- "version": "0.7.105",
3
+ "version": "0.7.108",
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",