@papi-ai/server 0.7.84 → 0.7.98

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.
@@ -30,7 +30,6 @@ function resolveDeploymentProfile(env) {
30
30
  dataEndpoint: cleanUrl(env.dataEndpoint) ?? (hostedSupabaseUrl ? `${hostedSupabaseUrl}/functions/v1/data-proxy` : void 0) ?? (selfHosted ? void 0 : `${HOSTED_SUPABASE_URL}/functions/v1/data-proxy`),
31
31
  providers: {
32
32
  vercel: optionalBoolean(env.vercelEnabled, !selfHosted),
33
- railway: optionalBoolean(env.railwayEnabled, !selfHosted),
34
33
  managedSupabase: optionalBoolean(env.managedSupabaseEnabled, !selfHosted)
35
34
  },
36
35
  auth: {
@@ -100,6 +99,21 @@ var init_dist = __esm({
100
99
  "../shared/dist/index.js"() {
101
100
  "use strict";
102
101
  CAPABILITY_REGISTRY = [
102
+ // task-3384: the PLAN step. The landing page has advertised these three since
103
+ // the v5 control beat (components/marketing/landing/ControlC.tsx) with a code
104
+ // comment reading "shipping soon, shown now" — the registry had no plan-step
105
+ // entry at all, so the Planning group rendered empty and was dropped. These
106
+ // are NOT new behaviour: each one gates a section of the planner instructions
107
+ // that already runs on every full-mode plan, so ON reproduces today exactly.
108
+ //
109
+ // All three default ON. Turning one off removes its step from the planner
110
+ // prompt, which is the whole mechanism — the planner does what the prompt
111
+ // tells it to, so an omitted step is genuinely not performed (and the matching
112
+ // plan-prepare progress checkpoint is not recorded, so the hub does not claim
113
+ // a step that never ran).
114
+ { key: "planHealthCheck", label: "Health check", description: "Flags cycle gaps, stale tasks and AD conflicts before planning.", step: "plan" },
115
+ { key: "backlogTriage", label: "Backlog triage", description: "Cleans up and prioritises unreviewed backlog items during planning.", step: "plan" },
116
+ { key: "buildOrder", label: "Build order", description: "Detects intra-cycle dependencies and sequences the build.", step: "plan" },
103
117
  { key: "prReviewer", label: "Auto code review", description: "Runs a code review of the branch diff before accepting.", step: "review" },
104
118
  { key: "securityScan", label: "Security scan", description: "Flags a security pass on risk-tier changes at review time.", step: "review" },
105
119
  { key: "changelog", label: "Changelog & cycle update", description: "Curates a cycle-update post when a release ships.", step: "release" },
@@ -135,12 +149,26 @@ var init_dist = __esm({
135
149
  { key: "autoBranch", label: "Auto branch", description: "Creates a feature branch per task/cycle at build start.", step: "build" },
136
150
  { key: "autoCommit", label: "Auto commit", description: "Commits your work automatically when a build completes.", step: "build" },
137
151
  { key: "autoPush", label: "Auto push & PR", description: "Pushes the branch and opens a pull request on build complete.", step: "build" },
138
- { key: "papiMetaFraming", label: "PAPI self-referential framing", description: "Includes PAPI-meta build-discipline framing in handoff output.", step: "build" }
152
+ { key: "papiMetaFraming", label: "PAPI self-referential framing", description: "Includes PAPI-meta build-discipline framing in handoff output.", step: "build" },
153
+ // Editor-equal release. Two legitimate team shapes need opposite answers, so
154
+ // this is a PROJECT policy rather than a global rule:
155
+ //
156
+ // OFF (default) — an editor's release opens a CONTRIBUTOR PR and PAPI records
157
+ // the release when GitHub merges it. Right for an open//multi-contributor
158
+ // product where the project owner is the last gate before production.
159
+ // ON — an editor releases on exactly the owner's path. Right for a small team
160
+ // of trusted peers who each run whole cycles end to end, where routing every
161
+ // close back through one person is the bottleneck, not the safeguard.
162
+ //
163
+ // Defaults OFF so existing projects are byte-identical, and because widening who
164
+ // can ship to production must be a deliberate choice, never a silent upgrade.
165
+ // A VIEWER is denied either way — this moves the editor line only.
166
+ { key: "editorRelease", label: "Editors release like the owner", description: "Lets editors run a full release instead of opening a contributor PR.", step: "release", defaultEnabled: false }
139
167
  ];
140
168
  CAPABILITY_KEYS = CAPABILITY_REGISTRY.map((c) => c.key);
141
169
  HOSTED_APP_URL = "https://getpapi.ai";
142
170
  HOSTED_MCP_URL = "https://mcp.getpapi.ai";
143
- HOSTED_SUPABASE_URL = "https://guewgygcpcmrcoppihzx.supabase.co";
171
+ HOSTED_SUPABASE_URL = "https://api.getpapi.ai";
144
172
  WAB_WINDOW_DAYS = 7;
145
173
  WAB_WEEK_MS = WAB_WINDOW_DAYS * 24 * 60 * 60 * 1e3;
146
174
  EFFORT_SCALE = {
@@ -171,6 +199,7 @@ __export(git_exports, {
171
199
  detectUnrecordedCommits: () => detectUnrecordedCommits,
172
200
  ensureLatestDevelop: () => ensureLatestDevelop,
173
201
  ensureTagAtHead: () => ensureTagAtHead,
202
+ findContributorReleasePullRequests: () => findContributorReleasePullRequests,
174
203
  findTaskCommitsOnBase: () => findTaskCommitsOnBase,
175
204
  getBranchDiff: () => getBranchDiff,
176
205
  getCommitFiles: () => getCommitFiles,
@@ -188,6 +217,7 @@ __export(git_exports, {
188
217
  getOriginRepoSlug: () => getOriginRepoSlug,
189
218
  getOriginUrl: () => getOriginUrl,
190
219
  getPathsDifferingFrom: () => getPathsDifferingFrom,
220
+ getPullRequestState: () => getPullRequestState,
191
221
  getPullRequestUrl: () => getPullRequestUrl,
192
222
  getRemoteBranchFiles: () => getRemoteBranchFiles,
193
223
  getRootCommitHash: () => getRootCommitHash,
@@ -1151,6 +1181,56 @@ function getPullRequestUrl(cwd, branch) {
1151
1181
  return null;
1152
1182
  }
1153
1183
  }
1184
+ function getPullRequestState(cwd, prUrl) {
1185
+ try {
1186
+ const output = execFileSync(
1187
+ "gh",
1188
+ ["pr", "view", prUrl, "--json", "url,state,mergedAt"],
1189
+ { cwd, encoding: "utf-8" }
1190
+ ).trim();
1191
+ if (!output) return null;
1192
+ const parsed = JSON.parse(output);
1193
+ if (!parsed.url || !["OPEN", "CLOSED", "MERGED"].includes(parsed.state ?? "")) return null;
1194
+ return {
1195
+ url: parsed.url,
1196
+ state: parsed.state,
1197
+ mergedAt: parsed.mergedAt ?? null
1198
+ };
1199
+ } catch {
1200
+ return null;
1201
+ }
1202
+ }
1203
+ function findContributorReleasePullRequests(cwd, cycle) {
1204
+ if (!Number.isInteger(cycle) || cycle <= 0) return [];
1205
+ try {
1206
+ const output = execFileSync(
1207
+ "gh",
1208
+ [
1209
+ "pr",
1210
+ "list",
1211
+ "--state",
1212
+ "all",
1213
+ "--limit",
1214
+ "20",
1215
+ "--search",
1216
+ `"Contributor release PR for cycle ${cycle}" in:body`,
1217
+ "--json",
1218
+ "url,state,mergedAt,headRefName"
1219
+ ],
1220
+ { cwd, encoding: "utf-8" }
1221
+ ).trim();
1222
+ if (!output) return [];
1223
+ const parsed = JSON.parse(output);
1224
+ return parsed.flatMap((pr) => pr.url && pr.headRefName && ["OPEN", "CLOSED", "MERGED"].includes(pr.state ?? "") ? [{
1225
+ url: pr.url,
1226
+ state: pr.state,
1227
+ mergedAt: pr.mergedAt ?? null,
1228
+ branch: pr.headRefName
1229
+ }] : []);
1230
+ } catch {
1231
+ return [];
1232
+ }
1233
+ }
1154
1234
  function squashMergePullRequest(cwd, branch) {
1155
1235
  const repo = getOriginRepoSlug(cwd);
1156
1236
  const baseArgs = ["pr", "merge", branch, "--squash", "--delete-branch"];
@@ -1405,6 +1485,16 @@ __export(proxy_adapter_exports, {
1405
1485
  createProxyAdapter: () => createProxyAdapter,
1406
1486
  wrapWithForwarding: () => wrapWithForwarding
1407
1487
  });
1488
+ function formatRateLimitError(route, response, errorBody, displayMessage = errorBody) {
1489
+ let reason;
1490
+ try {
1491
+ const parsed = JSON.parse(errorBody);
1492
+ if (typeof parsed.reason === "string") reason = parsed.reason;
1493
+ } catch {
1494
+ }
1495
+ const retryAfter = response.headers.get("Retry-After");
1496
+ return `Proxy rate limit on ${route}${reason ? ` (${reason})` : ""}${retryAfter ? `; retry after ${retryAfter}s` : ""}: ${displayMessage}`;
1497
+ }
1408
1498
  function snakeToCamel(str) {
1409
1499
  return str.replace(/_([a-z0-9])/g, (_, c) => c.toUpperCase());
1410
1500
  }
@@ -1446,7 +1536,7 @@ function wrapWithForwarding(instance) {
1446
1536
  return new Proxy(instance, {
1447
1537
  get(target, prop, receiver) {
1448
1538
  const existing = Reflect.get(target, prop, receiver);
1449
- if (existing !== void 0) return existing;
1539
+ if (Reflect.has(target, prop)) return existing;
1450
1540
  if (typeof prop !== "string" || prop === "then" || prop.startsWith("_") || NO_FORWARD.has(prop)) {
1451
1541
  return existing;
1452
1542
  }
@@ -1518,6 +1608,10 @@ var init_proxy_adapter = __esm({
1518
1608
  "dismissRecommendation",
1519
1609
  "findPendingDocActionsForTask",
1520
1610
  "getActiveDecisions",
1611
+ "getContributorRole",
1612
+ "listContributorReleasePrs",
1613
+ "recordContributorReleasePr",
1614
+ "setContributorReleasePrStatus",
1521
1615
  "getActiveStage",
1522
1616
  "getBuildReportCountForTask",
1523
1617
  "getBuildReportsSince",
@@ -1574,8 +1668,14 @@ var init_proxy_adapter = __esm({
1574
1668
  "linkPhasesToStage",
1575
1669
  "listContributors",
1576
1670
  "listConventions",
1671
+ "listDecisionPositions",
1672
+ "upsertDecisionPosition",
1673
+ "proposeDecision",
1674
+ "resolveDecisionState",
1675
+ "getUserEmailsByIds",
1577
1676
  "listMyBugReports",
1578
1677
  "listOwnerActionsForBlockerScan",
1678
+ "listOwnerActionsForOwner",
1579
1679
  "logEntityReferences",
1580
1680
  "markAgendaTopicsAddressed",
1581
1681
  "markCycleLearningResolved",
@@ -1639,6 +1739,12 @@ var init_proxy_adapter = __esm({
1639
1739
  // (1) local-only
1640
1740
  "close",
1641
1741
  "initRls",
1742
+ // task-3667: the exit-criteria evaluators probe adapter.sql to pick their
1743
+ // transport branch (direct SQL vs adapter reads). Forwarding `sql` to the
1744
+ // edge 403'd INSIDE sqlOf — every SQL-backed evaluator then threw
1745
+ // 'evaluator failed' instead of engaging its hosted branch or degrading to
1746
+ // an honest unevaluated reason. `sql` is local-only by definition.
1747
+ "sql",
1642
1748
  // task-3207 (C357): commitBuildComplete, commitReviewSubmit, commitRelease have no
1643
1749
  // edge case handler and no ALLOWED_METHODS entry — forwarding them 403s at the edge
1644
1750
  // with no try/catch at the call site, crashing build_execute/review_submit/release
@@ -1657,7 +1763,7 @@ var init_proxy_adapter = __esm({
1657
1763
  "applyActiveDecisionUpdates",
1658
1764
  // getToolCallCount + updatePhaseStatus are ABSENT: they now have edge handlers and
1659
1765
  // are served through the forwarder (getToolCallCount = SUP-2026-026 handler #1).
1660
- "getContributorRole",
1766
+ // getContributorRole is edge-wired and binds identity from the bearer.
1661
1767
  // task-3018 (C356): storeDocBody + getDocBodyUsage are now WIRED — edge case
1662
1768
  // handlers + ALLOWED_METHODS/WRITE_METHODS entries exist, so they forward and
1663
1769
  // hosted callers get body storage. Removed from this list, as task-3017 required.
@@ -1665,9 +1771,6 @@ var init_proxy_adapter = __esm({
1665
1771
  // of the owner-action queue. Six readers were wired C329 (task-2412) but the
1666
1772
  // producer stayed here, so the hosted Owner Action Queue was structurally empty
1667
1773
  // (AD-74 inverted). Now forwards; the edge binds user_id to the bearer ([C]).
1668
- "recordContributorReleasePr",
1669
- "setContributorReleasePrStatus",
1670
- "listContributorReleasePrs",
1671
1774
  "claimReview",
1672
1775
  "getSiblingAds",
1673
1776
  "getSiblingRepoTasks",
@@ -1728,6 +1831,15 @@ var init_proxy_adapter = __esm({
1728
1831
  this.projectId = config.projectId ?? "";
1729
1832
  this.onAuthRejected = config.onAuthRejected;
1730
1833
  }
1834
+ /**
1835
+ * Headers shared by every authenticated proxy route.
1836
+ */
1837
+ authenticatedHeaders() {
1838
+ return {
1839
+ "Content-Type": "application/json",
1840
+ "Authorization": `Bearer ${this.apiKey}`
1841
+ };
1842
+ }
1731
1843
  /**
1732
1844
  * Does the hosted path genuinely serve `name`? (task-3022, C362)
1733
1845
  *
@@ -1766,10 +1878,7 @@ var init_proxy_adapter = __esm({
1766
1878
  try {
1767
1879
  const response = await fetch(`${this.endpoint}/project-list`, {
1768
1880
  method: "POST",
1769
- headers: {
1770
- "Content-Type": "application/json",
1771
- "Authorization": `Bearer ${this.apiKey}`
1772
- },
1881
+ headers: this.authenticatedHeaders(),
1773
1882
  body: "{}",
1774
1883
  signal: AbortSignal.timeout(5e3)
1775
1884
  });
@@ -1808,10 +1917,7 @@ var init_proxy_adapter = __esm({
1808
1917
  const url = `${this.endpoint}/ensure-project`;
1809
1918
  const response = await fetch(url, {
1810
1919
  method: "POST",
1811
- headers: {
1812
- "Content-Type": "application/json",
1813
- "Authorization": `Bearer ${this.apiKey}`
1814
- },
1920
+ headers: this.authenticatedHeaders(),
1815
1921
  body: JSON.stringify({
1816
1922
  ...projectName ? { projectName } : {},
1817
1923
  ...repoUrl ? { repoUrl } : {},
@@ -1831,6 +1937,9 @@ var init_proxy_adapter = __esm({
1831
1937
  } catch {
1832
1938
  message = errorBody;
1833
1939
  }
1940
+ if (response.status === 429) {
1941
+ throw new Error(formatRateLimitError("ensureProject", response, errorBody, message));
1942
+ }
1834
1943
  throw new Error(`Auto-provision failed (${response.status}): ${message}`);
1835
1944
  }
1836
1945
  const body = await response.json();
@@ -1851,10 +1960,7 @@ var init_proxy_adapter = __esm({
1851
1960
  try {
1852
1961
  response = await fetch(url, {
1853
1962
  method: "POST",
1854
- headers: {
1855
- "Content-Type": "application/json",
1856
- "Authorization": `Bearer ${this.apiKey}`
1857
- },
1963
+ headers: this.authenticatedHeaders(),
1858
1964
  body: JSON.stringify({
1859
1965
  projectId: this.projectId,
1860
1966
  method,
@@ -1897,6 +2003,9 @@ Check PAPI_PROJECT_ID in your .mcp.json config. Find your project ID in the PAPI
1897
2003
  (${response.status} on ${method}: ${message})`
1898
2004
  );
1899
2005
  }
2006
+ if (response.status === 429) {
2007
+ throw new Error(formatRateLimitError(method, response, errorBody, message));
2008
+ }
1900
2009
  throw new Error(`Proxy error (${response.status}) on ${method}: ${message}`);
1901
2010
  }
1902
2011
  const body = await response.json();
@@ -1958,10 +2067,7 @@ Check PAPI_PROJECT_ID in your .mcp.json config. Find your project ID in the PAPI
1958
2067
  try {
1959
2068
  const response = await fetch(`${this.endpoint}/invoke`, {
1960
2069
  method: "POST",
1961
- headers: {
1962
- "Content-Type": "application/json",
1963
- "Authorization": `Bearer ${this.apiKey}`
1964
- },
2070
+ headers: this.authenticatedHeaders(),
1965
2071
  body: JSON.stringify({ projectId, method: "projectExists", args: [] }),
1966
2072
  signal: AbortSignal.timeout(5e3)
1967
2073
  });
@@ -1992,8 +2098,8 @@ Check PAPI_PROJECT_ID in your .mcp.json config. Find your project ID in the PAPI
1992
2098
  writeCycleLogEntry(entry) {
1993
2099
  return this.invoke("writeCycleLogEntry", [entry]);
1994
2100
  }
1995
- updateActiveDecision(id, body, cycleNumber) {
1996
- return this.invoke("updateActiveDecision", [id, body, cycleNumber]);
2101
+ updateActiveDecision(id, body, cycleNumber, action) {
2102
+ return this.invoke("updateActiveDecision", [id, body, cycleNumber, action]);
1997
2103
  }
1998
2104
  upsertActiveDecision(id, body, title, confidence, cycleNumber) {
1999
2105
  return this.invoke("upsertActiveDecision", [id, body, title, confidence, cycleNumber]);
@@ -2120,7 +2226,7 @@ Check PAPI_PROJECT_ID in your .mcp.json config. Find your project ID in the PAPI
2120
2226
  * task-2288 (C308): emit a telemetry event via the proxy's /telemetry endpoint
2121
2227
  * using THIS adapter's per-request bearer (this.apiKey) — the same credential
2122
2228
  * invoke() uses. This is the fix for the hosted-transport blackout: on the
2123
- * multi-tenant Railway pod there is no PAPI_DATA_API_KEY env var, so the
2229
+ * multi-tenant server has no PAPI_DATA_API_KEY env var, so the
2124
2230
  * env-var emitTelemetryEvent path (lib/telemetry.ts) dropped every event since
2125
2231
  * 2026-06-16. Fire-and-forget — never throws, never blocks the tool response.
2126
2232
  * Not routed through invoke() because /telemetry is a distinct endpoint and a
@@ -2136,10 +2242,7 @@ Check PAPI_PROJECT_ID in your .mcp.json config. Find your project ID in the PAPI
2136
2242
  if (event.projectId) payload["projectId"] = event.projectId;
2137
2243
  fetch(`${this.endpoint}/telemetry`, {
2138
2244
  method: "POST",
2139
- headers: {
2140
- "Content-Type": "application/json",
2141
- "Authorization": `Bearer ${this.apiKey}`
2142
- },
2245
+ headers: this.authenticatedHeaders(),
2143
2246
  body: JSON.stringify(payload),
2144
2247
  signal: AbortSignal.timeout(5e3)
2145
2248
  }).then((res) => {
@@ -2226,6 +2329,19 @@ Check PAPI_PROJECT_ID in your .mcp.json config. Find your project ID in the PAPI
2226
2329
  getLatestDecisionScores() {
2227
2330
  return this.invoke("getLatestDecisionScores");
2228
2331
  }
2332
+ // --- Decision lifecycle — propose / resolve / positions (task-3408, C366) ---
2333
+ proposeDecision(id, decisionType, proposedByUserId, cycleNumber) {
2334
+ return this.invoke("proposeDecision", [id, decisionType, proposedByUserId, cycleNumber]);
2335
+ }
2336
+ resolveDecisionState(id, state, actorUserId, cycleNumber) {
2337
+ return this.invoke("resolveDecisionState", [id, state, actorUserId, cycleNumber]);
2338
+ }
2339
+ listDecisionPositions(decisionId) {
2340
+ return this.invoke("listDecisionPositions", [decisionId]);
2341
+ }
2342
+ upsertDecisionPosition(decisionId, userId, status, comments) {
2343
+ return this.invoke("upsertDecisionPosition", [decisionId, userId, status, comments ?? null]);
2344
+ }
2229
2345
  logEntityReferences(refs) {
2230
2346
  return this.invoke("logEntityReferences", [refs]);
2231
2347
  }
@@ -2383,8 +2499,8 @@ Check PAPI_PROJECT_ID in your .mcp.json config. Find your project ID in the PAPI
2383
2499
  getCycleLearningPatterns() {
2384
2500
  return this.invoke("getCycleLearningPatterns", []);
2385
2501
  }
2386
- updateCycleLearningActionRef(learningId, taskDisplayId) {
2387
- return this.invoke("updateCycleLearningActionRef", [learningId, taskDisplayId]);
2502
+ updateCycleLearningActionRef(learningId, taskDisplayId, opts) {
2503
+ return this.invoke("updateCycleLearningActionRef", [learningId, taskDisplayId, opts ?? {}]);
2388
2504
  }
2389
2505
  // --- Strategy Review Drafts ---
2390
2506
  savePendingReviewResponse(cycleNumber, rawResponse) {
@@ -2421,7 +2537,18 @@ Check PAPI_PROJECT_ID in your .mcp.json config. Find your project ID in the PAPI
2421
2537
  }
2422
2538
  // --- Project metadata (path-identity guardrail) ---
2423
2539
  async getProjectInfo() {
2424
- return this.invoke("getProjectInfo", []);
2540
+ const raw = await this.invoke("getProjectInfo", []);
2541
+ if (!raw) return null;
2542
+ return {
2543
+ name: raw.name,
2544
+ slug: raw.slug,
2545
+ papi_dir: raw.papi_dir ?? raw.papiDir ?? null,
2546
+ repo_url: raw.repo_url ?? raw.repoUrl ?? null,
2547
+ capabilities: raw.capabilities ?? {},
2548
+ discord_channel_id: raw.discord_channel_id ?? raw.discordChannelId ?? null,
2549
+ decision_resolution_mode: raw.decision_resolution_mode ?? raw.decisionResolutionMode ?? "owner",
2550
+ decision_type_resolvers: raw.decision_type_resolvers ?? raw.decisionTypeResolvers ?? {}
2551
+ };
2425
2552
  }
2426
2553
  /**
2427
2554
  * task-2052 (C288): owner-gate identity for the proxy transport. Both sides
@@ -2461,10 +2588,7 @@ Check PAPI_PROJECT_ID in your .mcp.json config. Find your project ID in the PAPI
2461
2588
  async postRoute(route, payload) {
2462
2589
  const response = await fetch(`${this.endpoint}/${route}`, {
2463
2590
  method: "POST",
2464
- headers: {
2465
- "Content-Type": "application/json",
2466
- "Authorization": `Bearer ${this.apiKey}`
2467
- },
2591
+ headers: this.authenticatedHeaders(),
2468
2592
  body: JSON.stringify(payload),
2469
2593
  signal: AbortSignal.timeout(15e3)
2470
2594
  });
@@ -2476,6 +2600,9 @@ Check PAPI_PROJECT_ID in your .mcp.json config. Find your project ID in the PAPI
2476
2600
  } catch {
2477
2601
  message = errorBody;
2478
2602
  }
2603
+ if (response.status === 429) {
2604
+ throw new Error(formatRateLimitError(route, response, errorBody, message));
2605
+ }
2479
2606
  if (response.status === 401) this.onAuthRejected?.();
2480
2607
  throw new Error(`Proxy error (${response.status}) on ${route}: ${message}`);
2481
2608
  }
@@ -2490,7 +2617,11 @@ Check PAPI_PROJECT_ID in your .mcp.json config. Find your project ID in the PAPI
2490
2617
  */
2491
2618
  async getMeteredUsage() {
2492
2619
  const body = await this.postRoute("metering", {});
2493
- return { tier: body.tier ?? "free", monthlyToolCalls: body.monthlyToolCalls ?? 0 };
2620
+ return {
2621
+ tier: body.tier ?? "free",
2622
+ monthlyToolCalls: body.monthlyToolCalls ?? 0,
2623
+ projectLimitOverride: body.projectLimitOverride ?? null
2624
+ };
2494
2625
  }
2495
2626
  async listUserProjects() {
2496
2627
  const body = await this.postRoute("project-list", {});
@@ -2621,7 +2752,6 @@ function serverDeploymentProfile() {
2621
2752
  dataEndpoint: process.env["PAPI_DATA_ENDPOINT"],
2622
2753
  hostedSupabaseUrl: process.env["PAPI_HOSTED_SUPABASE_URL"],
2623
2754
  vercelEnabled: process.env["PAPI_ENABLE_VERCEL"],
2624
- railwayEnabled: process.env["PAPI_ENABLE_RAILWAY"],
2625
2755
  managedSupabaseEnabled: process.env["PAPI_ENABLE_MANAGED_SUPABASE"]
2626
2756
  });
2627
2757
  }
@@ -2792,16 +2922,21 @@ Fix the PAPI_PROJECT_ID in your .mcp.json (or unset it and let PAPI resolve the
2792
2922
  });
2793
2923
  }
2794
2924
  } else {
2795
- if (!existing.root_commit_hash && rootHash) {
2925
+ if (!existing.root_commit_hash && rootHash || !existing.repo_url && originUrl) {
2796
2926
  try {
2797
- await pgAdapter.updateProject(projectId, {
2798
- root_commit_hash: rootHash,
2799
- repo_url: originUrl ?? void 0,
2800
- resolution_method: "root_hash"
2801
- });
2927
+ const update = {};
2928
+ if (!existing.root_commit_hash && rootHash) {
2929
+ update.root_commit_hash = rootHash;
2930
+ update.resolution_method = "root_hash";
2931
+ }
2932
+ if (!existing.repo_url && originUrl) {
2933
+ update.repo_url = originUrl;
2934
+ if (!update.resolution_method && !existing.resolution_method) update.resolution_method = "repo_url";
2935
+ }
2936
+ await pgAdapter.updateProject(projectId, update);
2802
2937
  } catch (err) {
2803
2938
  console.error(
2804
- `[papi] \u26A0 Failed to backfill root_commit_hash: ${err instanceof Error ? err.message : String(err)}`
2939
+ `[papi] \u26A0 Failed to backfill project resolution signals: ${err instanceof Error ? err.message : String(err)}`
2805
2940
  );
2806
2941
  }
2807
2942
  }
@@ -2842,7 +2977,7 @@ Fix the PAPI_PROJECT_ID in your .mcp.json (or unset it and let PAPI resolve the
2842
2977
  } catch {
2843
2978
  }
2844
2979
  }
2845
- const adapter = new PgPapiAdapter(config, projectId);
2980
+ const adapter = new PgPapiAdapter({ ...config, userId: resolveUserId }, projectId);
2846
2981
  try {
2847
2982
  await adapter.initRls();
2848
2983
  } catch {
@@ -2871,7 +3006,7 @@ Fix the PAPI_PROJECT_ID in your .mcp.json (or unset it and let PAPI resolve the
2871
3006
  Get started in 3 steps:
2872
3007
  1. Sign up at ${dashboardUrl ? `${dashboardUrl}/login` : "your configured PAPI dashboard"}
2873
3008
  2. Complete the onboarding wizard \u2014 it generates your .mcp.json config
2874
- 3. Download the config, place it in your project root, and restart Claude Code
3009
+ 3. Download the config, place it in your project root, and restart your AI client
2875
3010
 
2876
3011
  Already have an account? Make sure PAPI_DATA_API_KEY is set in your .mcp.json env config.`
2877
3012
  );